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

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

from ccxt.base.exchange import Exchange

# -----------------------------------------------------------------------------

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 BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
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.errors import OnMaintenance
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
from ccxt.base.precise import Precise


class bittrex(Exchange):

    def describe(self):
        return self.deep_extend(super(bittrex, self).describe(), {
            'id': 'bittrex',
            'name': 'Bittrex',
            'countries': ['US'],
            'version': 'v3',
            'rateLimit': 1500,
            'certified': False,
            'pro': True,
            # new metainfo interface
            'has': {
                'CORS': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createDepositAddress': True,
                'createMarketOrder': True,
                'createOrder': True,
                'fetchBalance': True,
                'fetchDeposits': True,
                'fetchDepositAddress': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchMarkets': True,
                'fetchMyTrades': 'emulated',
                'fetchOHLCV': True,
                'fetchOrder': True,
                'fetchOrderTrades': True,
                'fetchOrderBook': True,
                'fetchOpenOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTransactions': False,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': 'MINUTE_1',
                '5m': 'MINUTE_5',
                '1h': 'HOUR_1',
                '1d': 'DAY_1',
            },
            'hostname': 'bittrex.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87153921-edf53180-c2c0-11ea-96b9-f2a9a95a455b.jpg',
                'api': {
                    'public': 'https://api.bittrex.com',
                    'private': 'https://api.bittrex.com',
                },
                'www': 'https://bittrex.com',
                'doc': [
                    'https://bittrex.github.io/api/v3',
                ],
                '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-',
                ],
                'referral': 'https://bittrex.com/Account/Register?referralCode=1ZE-G0G-M3B',
            },
            'api': {
                'public': {
                    'get': [
                        'ping',
                        'currencies',
                        'currencies/{symbol}',
                        'markets',
                        'markets/tickers',
                        'markets/summaries',
                        'markets/{marketSymbol}',
                        'markets/{marketSymbol}/summary',
                        'markets/{marketSymbol}/orderbook',
                        'markets/{marketSymbol}/trades',
                        'markets/{marketSymbol}/ticker',
                        'markets/{marketSymbol}/candles/{candleInterval}/recent',
                        'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}/{month}/{day}',
                        'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}/{month}',
                        'markets/{marketSymbol}/candles/{candleInterval}/historical/{year}',
                    ],
                },
                'private': {
                    'get': [
                        'account',
                        'account/volume',
                        'addresses',
                        'addresses/{currencySymbol}',
                        'balances',
                        'balances/{currencySymbol}',
                        'deposits/open',
                        'deposits/closed',
                        'deposits/ByTxId/{txId}',
                        'deposits/{depositId}',
                        'orders/closed',
                        'orders/open',
                        'orders/{orderId}',
                        'orders/{orderId}/executions',
                        'ping',
                        'subaccounts/{subaccountId}',
                        'subaccounts',
                        'withdrawals/open',
                        'withdrawals/closed',
                        'withdrawals/ByTxId/{txId}',
                        'withdrawals/{withdrawalId}',
                        'withdrawals/whitelistAddresses',
                        'conditional-orders/{conditionalOrderId}',
                        'conditional-orders/closed',
                        'conditional-orders/open',
                        'transfers/sent',
                        'transfers/received',
                        'transfers/{transferId}',
                    ],
                    'post': [
                        'addresses',
                        'orders',
                        'subaccounts',
                        'withdrawals',
                        'conditional-orders',
                        'transfers',
                    ],
                    'delete': [
                        'orders/open',
                        'orders/{orderId}',
                        'withdrawals/{withdrawalId}',
                        'conditional-orders/{conditionalOrderId}',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'maker': 0.0035,
                    'taker': 0.0035,
                },
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                },
            },
            'exceptions': {
                'exact': {
                    'BAD_REQUEST': BadRequest,  # {"code":"BAD_REQUEST","detail":"Refer to the data field for specific field validation failures.","data":{"invalidRequestParameter":"day"}}
                    'STARTDATE_OUT_OF_RANGE': BadRequest,  # {"code":"STARTDATE_OUT_OF_RANGE"}
                    # '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,
                    'INVALID_CEILING_MARKET_BUY': InvalidOrder,
                    'INVALID_FIAT_ACCOUNT': InvalidOrder,
                    'INVALID_ORDER_TYPE': InvalidOrder,
                    '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)
                    'INVALID_MARKET': BadSymbol,  # {"success":false,"message":"INVALID_MARKET","result":null,"explanation":null}
                    'WHITELIST_VIOLATION_IP': PermissionDenied,
                    'DUST_TRADE_DISALLOWED_MIN_VALUE': InvalidOrder,
                    'RESTRICTED_MARKET': BadSymbol,
                    'We are down for scheduled maintenance, but we\u2019ll be back up shortly.': OnMaintenance,  # {"success":false,"message":"We are down for scheduled maintenance, but we\u2019ll be back up shortly.","result":null,"explanation":null}
                },
                'broad': {
                    'throttled': DDoSProtection,
                    'problem': ExchangeNotAvailable,
                },
            },
            'options': {
                'fetchTicker': {
                    'method': 'publicGetMarketsMarketSymbolTicker',  # publicGetMarketsMarketSymbolSummary
                },
                'fetchTickers': {
                    'method': 'publicGetMarketsTickers',  # publicGetMarketsSummaries
                },
                'parseOrderStatus': False,
                'hasAlreadyAuthenticatedSuccessfully': False,  # a workaround for APIKEY_INVALID
                # 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,
                # 'createOrderMethod': 'create_order_v1',
            },
            'commonCurrencies': {
                'REPV2': 'REP',
            },
        })

    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)

    def fetch_markets(self, params={}):
        response = self.publicGetMarkets(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 = []
        for i in range(0, len(response)):
            market = response[i]
            baseId = self.safe_string(market, 'baseCurrencySymbol')
            quoteId = self.safe_string(market, 'quoteCurrencySymbol')
            id = self.safe_string(market, 'symbol')
            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_number(market, 'minTradeSize'),
                        'max': None,
                    },
                    'price': {
                        'min': 1 / math.pow(10, precision['price']),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
            })
        return result

    def fetch_balance(self, params={}):
        self.load_markets()
        balances = self.privateGetBalances(params)
        result = {'info': balances}
        indexed = self.index_by(balances, 'currencySymbol')
        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_string(balance, 'available')
            account['total'] = self.safe_string(balance, 'total')
            result[code] = account
        return self.parse_balance(result, False)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        request = {
            'marketSymbol': self.market_id(symbol),
        }
        if limit is not None:
            if (limit != 1) and (limit != 25) and (limit != 500):
                raise BadRequest(self.id + ' fetchOrderBook() limit argument must be None, 1, 25 or 500, default is 25')
            request['depth'] = limit
        response = self.publicGetMarketsMarketSymbolOrderbook(self.extend(request, params))
        #
        #     {
        #         "bid":[
        #             {"quantity":"0.01250000","rate":"10718.56200003"},
        #             {"quantity":"0.10000000","rate":"10718.56200002"},
        #             {"quantity":"0.39648292","rate":"10718.56200001"},
        #         ],
        #         "ask":[
        #             {"quantity":"0.05100000","rate":"10724.30099631"},
        #             {"quantity":"0.10000000","rate":"10724.30099632"},
        #             {"quantity":"0.26000000","rate":"10724.30099634"},
        #         ]
        #     }
        #
        sequence = self.safe_integer(self.last_response_headers, 'Sequence')
        orderbook = self.parse_order_book(response, symbol, None, 'bid', 'ask', 'rate', 'quantity')
        orderbook['nonce'] = sequence
        return orderbook

    def fetch_currencies(self, params={}):
        response = self.publicGetCurrencies(params)
        #
        #     [
        #         {
        #             "symbol":"1ST",
        #             "name":"Firstblood",
        #             "coinType":"ETH_CONTRACT",
        #             "status":"ONLINE",
        #             "minConfirmations":36,
        #             "notice":"",
        #             "txFee":"4.50000000",
        #             "logoUrl":"https://bittrexblobstorage.blob.core.windows.net/public/5685a7be-1edf-4ba0-a313-b5309bb204f8.png",
        #             "prohibitedIn":[],
        #             "baseAddress":"0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98",
        #             "associatedTermsOfService":[]
        #         }
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'symbol')
            code = self.safe_currency_code(id)
            precision = 8  # default precision, todo: fix "magic constants"
            fee = self.safe_number(currency, 'txFee')  # todo: redesign
            isActive = self.safe_string(currency, 'status')
            result[code] = {
                'id': id,
                'code': code,
                'address': self.safe_string(currency, 'baseAddress'),
                'info': currency,
                'type': self.safe_string(currency, 'coinType'),
                'name': self.safe_string(currency, 'name'),
                'active': (isActive == 'ONLINE'),
                'fee': fee,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': 1 / math.pow(10, precision),
                        'max': None,
                    },
                    'withdraw': {
                        'min': fee,
                        'max': None,
                    },
                },
            }
        return result

    def parse_ticker(self, ticker, market=None):
        #
        # ticker
        #
        #     {
        #         "symbol":"ETH-BTC",
        #         "lastTradeRate":"0.03284496",
        #         "bidRate":"0.03284523",
        #         "askRate":"0.03286857"
        #     }
        #
        # summary
        #
        #     {
        #         "symbol":"ETH-BTC",
        #         "high":"0.03369528",
        #         "low":"0.03282442",
        #         "volume":"4307.83794556",
        #         "quoteVolume":"143.08608869",
        #         "percentChange":"0.79",
        #         "updatedAt":"2020-09-29T07:36:57.823Z"
        #     }
        #
        timestamp = self.parse8601(self.safe_string(ticker, 'updatedAt'))
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market, '-')
        percentage = self.safe_number(ticker, 'percentChange')
        last = self.safe_number(ticker, 'lastTradeRate')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'high'),
            'low': self.safe_number(ticker, 'low'),
            'bid': self.safe_number(ticker, 'bidRate'),
            'bidVolume': None,
            'ask': self.safe_number(ticker, 'askRate'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_number(ticker, 'volume'),
            'quoteVolume': self.safe_number(ticker, 'quoteVolume'),
            'info': ticker,
        }

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        options = self.safe_value(self.options, 'fetchTickers', {})
        defaultMethod = self.safe_string(options, 'method', 'publicGetMarketsTickers')
        method = self.safe_string(params, 'method', defaultMethod)
        params = self.omit(params, 'method')
        response = getattr(self, method)(params)
        #
        # publicGetMarketsTickers
        #
        #     [
        #         {
        #             "symbol":"4ART-BTC",
        #             "lastTradeRate":"0.00000210",
        #             "bidRate":"0.00000210",
        #             "askRate":"0.00000215"
        #         }
        #     ]
        #
        # publicGetMarketsSummaries
        #
        #     [
        #         {
        #             "symbol":"4ART-BTC",
        #             "high":"0.00000206",
        #             "low":"0.00000196",
        #             "volume":"14871.32000233",
        #             "quoteVolume":"0.02932756",
        #             "percentChange":"1.48",
        #             "updatedAt":"2020-09-29T07:34:32.757Z"
        #         }
        #     ]
        #
        tickers = []
        for i in range(0, len(response)):
            ticker = self.parse_ticker(response[i])
            tickers.append(ticker)
        return self.filter_by_array(tickers, 'symbol', symbols)

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'marketSymbol': market['id'],
        }
        options = self.safe_value(self.options, 'fetchTicker', {})
        defaultMethod = self.safe_string(options, 'method', 'publicGetMarketsMarketSymbolTicker')
        method = self.safe_string(params, 'method', defaultMethod)
        params = self.omit(params, 'method')
        response = getattr(self, method)(self.extend(request, params))
        #
        # publicGetMarketsMarketSymbolTicker
        #
        #     {
        #         "symbol":"ETH-BTC",
        #         "lastTradeRate":"0.03284496",
        #         "bidRate":"0.03284523",
        #         "askRate":"0.03286857"
        #     }
        #
        #
        # publicGetMarketsMarketSymbolSummary
        #
        #     {
        #         "symbol":"ETH-BTC",
        #         "high":"0.03369528",
        #         "low":"0.03282442",
        #         "volume":"4307.83794556",
        #         "quoteVolume":"143.08608869",
        #         "percentChange":"0.79",
        #         "updatedAt":"2020-09-29T07:36:57.823Z"
        #     }
        #
        return self.parse_ticker(response, market)

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #     {
        #         "id":"9c5589db-42fb-436c-b105-5e2edcb95673",
        #         "executedAt":"2020-10-03T11:48:43.38Z",
        #         "quantity":"0.17939626",
        #         "rate":"0.03297952",
        #         "takerSide":"BUY"
        #     }
        #
        # private fetchOrderTrades
        #
        #     {
        #         "id": "aaa3e9bd-5b86-4a21-8b3d-1275c1d30b8e",
        #         "marketSymbol": "OMG-BTC",
        #         "executedAt": "2020-10-02T16:00:30.3Z",
        #         "quantity": "7.52710000",
        #         "rate": "0.00034907",
        #         "orderId": "3a3dbd33-3a30-4ae5-a41d-68d3c1ac537e",
        #         "commission": "0.00000525",
        #         "isTaker": False
        #     }
        #
        timestamp = self.parse8601(self.safe_string(trade, 'executedAt'))
        id = self.safe_string(trade, 'id')
        order = self.safe_string(trade, 'orderId')
        marketId = self.safe_string(trade, 'marketSymbol')
        market = self.safe_market(marketId, market, '-')
        priceString = self.safe_string(trade, 'rate')
        amountString = self.safe_string(trade, 'quantity')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.parse_number(Precise.string_mul(priceString, amountString))
        takerOrMaker = None
        isTaker = self.safe_value(trade, 'isTaker')
        if isTaker is not None:
            takerOrMaker = 'taker' if isTaker else 'maker'
        fee = None
        feeCost = self.safe_number(trade, 'commission')
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': market['quote'],
            }
        side = self.safe_string_lower(trade, 'takerSide')
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'id': id,
            'order': order,
            'takerOrMaker': takerOrMaker,
            'type': None,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_time(self, params={}):
        response = self.publicGetPing(params)
        #
        #     {
        #         "serverTime": 1594596023162
        #     }
        #
        return self.safe_integer(response, 'serverTime')

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'marketSymbol': self.market_id(symbol),
        }
        response = self.publicGetMarketsMarketSymbolTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"9c5589db-42fb-436c-b105-5e2edcb95673",
        #             "executedAt":"2020-10-03T11:48:43.38Z",
        #             "quantity":"0.17939626",
        #             "rate":"0.03297952",
        #             "takerSide":"BUY"
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "startsAt":"2020-06-12T02:35:00Z",
        #         "open":"0.02493753",
        #         "high":"0.02493753",
        #         "low":"0.02493753",
        #         "close":"0.02493753",
        #         "volume":"0.09590123",
        #         "quoteVolume":"0.00239153"
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'startsAt')),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'volume'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        reverseId = market['baseId'] + '-' + market['quoteId']
        request = {
            'candleInterval': self.timeframes[timeframe],
            'marketSymbol': reverseId,
        }
        method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalRecent'
        if since is not None:
            now = self.milliseconds()
            difference = abs(now - since)
            sinceDate = self.ymd(since)
            parts = sinceDate.split('-')
            sinceYear = self.safe_integer(parts, 0)
            sinceMonth = self.safe_integer(parts, 1)
            sinceDay = self.safe_integer(parts, 2)
            if timeframe == '1d':
                # if the since argument is beyond one year into the past
                if difference > 31622400000:
                    method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYear'
                    request['year'] = sinceYear
                # request['year'] = year
            elif timeframe == '1h':
                # if the since argument is beyond 31 days into the past
                if difference > 2678400000:
                    method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYearMonth'
                    request['year'] = sinceYear
                    request['month'] = sinceMonth
            else:
                # if the since argument is beyond 1 day into the past
                if difference > 86400000:
                    method = 'publicGetMarketsMarketSymbolCandlesCandleIntervalHistoricalYearMonthDay'
                    request['year'] = sinceYear
                    request['month'] = sinceMonth
                    request['day'] = sinceDay
        response = getattr(self, method)(self.extend(request, params))
        #
        #     [
        #         {"startsAt":"2020-06-12T02:35:00Z","open":"0.02493753","high":"0.02493753","low":"0.02493753","close":"0.02493753","volume":"0.09590123","quoteVolume":"0.00239153"},
        #         {"startsAt":"2020-06-12T02:40:00Z","open":"0.02491874","high":"0.02491874","low":"0.02490970","close":"0.02490970","volume":"0.04515695","quoteVolume":"0.00112505"},
        #         {"startsAt":"2020-06-12T02:45:00Z","open":"0.02490753","high":"0.02493143","low":"0.02490753","close":"0.02493143","volume":"0.17769640","quoteVolume":"0.00442663"}
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['marketSymbol'] = market['id']
        response = self.privateGetOrdersOpen(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'orderId': id,
        }
        response = self.privateGetOrdersOrderIdExecutions(self.extend(request, params))
        market = None
        if symbol is not None:
            market = self.market(symbol)
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        # A ceiling order is a market or limit order that allows you to specify
        # the amount of quote currency you want to spend(or receive, if selling)
        # instead of the quantity of the market currency(e.g. buy $100 USD of BTC
        # at the current market BTC price)
        self.load_markets()
        market = self.market(symbol)
        uppercaseType = type.upper()
        reverseId = market['baseId'] + '-' + market['quoteId']
        request = {
            'marketSymbol': reverseId,
            'direction': side.upper(),
            'type': uppercaseType,  # LIMIT, MARKET, CEILING_LIMIT, CEILING_MARKET
            # 'quantity': self.amount_to_precision(symbol, amount),  # required for limit orders, excluded for ceiling orders
            # 'ceiling': self.price_to_precision(symbol, price),  # required for ceiling orders, excluded for non-ceiling orders
            # 'limit': self.price_to_precision(symbol, price),  # required for limit orders, excluded for market orders
            # 'timeInForce': 'GOOD_TIL_CANCELLED',  # IMMEDIATE_OR_CANCEL, FILL_OR_KILL, POST_ONLY_GOOD_TIL_CANCELLED
            # 'useAwards': False,  # optional
        }
        isCeilingLimit = (uppercaseType == 'CEILING_LIMIT')
        isCeilingMarket = (uppercaseType == 'CEILING_MARKET')
        isCeilingOrder = isCeilingLimit or isCeilingMarket
        if isCeilingOrder:
            cost = None
            if isCeilingLimit:
                request['limit'] = self.price_to_precision(symbol, price)
                cost = self.safe_number_2(params, 'ceiling', 'cost', amount)
            elif isCeilingMarket:
                cost = self.safe_number_2(params, 'ceiling', 'cost')
                if cost is None:
                    if price is None:
                        cost = amount
                    else:
                        cost = amount * price
            params = self.omit(params, ['ceiling', 'cost'])
            request['ceiling'] = self.cost_to_precision(symbol, cost)
            # bittrex only accepts IMMEDIATE_OR_CANCEL or FILL_OR_KILL for ceiling orders
            request['timeInForce'] = 'IMMEDIATE_OR_CANCEL'
        else:
            request['quantity'] = self.amount_to_precision(symbol, amount)
            if uppercaseType == 'LIMIT':
                request['limit'] = self.price_to_precision(symbol, price)
                request['timeInForce'] = 'GOOD_TIL_CANCELLED'
            else:
                # bittrex does not allow GOOD_TIL_CANCELLED for market orders
                request['timeInForce'] = 'IMMEDIATE_OR_CANCEL'
        response = self.privatePostOrders(self.extend(request, params))
        #
        #     {
        #         id: 'f03d5e98-b5ac-48fb-8647-dd4db828a297',
        #         marketSymbol: 'BTC-USDT',
        #         direction: 'SELL',
        #         type: 'LIMIT',
        #         quantity: '0.01',
        #         limit: '6000',
        #         timeInForce: 'GOOD_TIL_CANCELLED',
        #         fillQuantity: '0.00000000',
        #         commission: '0.00000000',
        #         proceeds: '0.00000000',
        #         status: 'OPEN',
        #         createdAt: '2020-03-18T02:37:33.42Z',
        #         updatedAt: '2020-03-18T02:37:33.42Z'
        #       }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            'orderId': id,
        }
        response = self.privateDeleteOrdersOrderId(self.extend(request, params))
        return self.extend(self.parse_order(response), {
            'id': id,
            'info': response,
            'status': 'canceled',
        })

    def cancel_all_orders(self, symbol=None, params={}):
        self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['marketSymbol'] = market['id']
        response = self.privateDeleteOrdersOpen(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"66582be0-5337-4d8c-b212-c356dd525801",
        #             "statusCode":"SUCCESS",
        #             "result":{
        #                 "id":"66582be0-5337-4d8c-b212-c356dd525801",
        #                 "marketSymbol":"BTC-USDT",
        #                 "direction":"BUY",
        #                 "type":"LIMIT",
        #                 "quantity":"0.01000000",
        #                 "limit":"3000.00000000",
        #                 "timeInForce":"GOOD_TIL_CANCELLED",
        #                 "fillQuantity":"0.00000000",
        #                 "commission":"0.00000000",
        #                 "proceeds":"0.00000000",
        #                 "status":"CLOSED",
        #                 "createdAt":"2020-10-06T12:31:53.39Z",
        #                 "updatedAt":"2020-10-06T12:54:28.8Z",
        #                 "closedAt":"2020-10-06T12:54:28.8Z"
        #             }
        #         }
        #     ]
        #
        orders = []
        for i in range(0, len(response)):
            result = self.safe_value(response[i], 'result', {})
            orders.append(result)
        return self.parse_orders(orders, market)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        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['currencySymbol'] = currency['id']
        response = self.privateGetDepositsClosed(self.extend(request, params))
        # we cannot filter by `since` timestamp, as it isn't set by Bittrex
        # see https://github.com/ccxt/ccxt/issues/4067
        # return self.parse_transactions(response, currency, since, limit)
        return self.parse_transactions(response, currency, None, limit)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        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['currencySymbol'] = currency['id']
        response = self.privateGetWithdrawalsClosed(self.extend(request, params))
        return self.parse_transactions(response, currency, since, limit)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #     {
        #         "id": "d00fdf2e-df9e-48f1-....",
        #         "currencySymbol": "BTC",
        #         "quantity": "0.00550000",
        #         "cryptoAddress": "1PhmYjnJPZH5NUwV8AU...",
        #         "txId": "d1f1afffe1b9b6614eaee7e8133c85d98...",
        #         "confirmations": 2,
        #         "updatedAt": "2020-01-12T16:49:30.41Z",
        #         "completedAt": "2020-01-12T16:49:30.41Z",
        #         "status": "COMPLETED",
        #         "source": "BLOCKCHAIN"
        #     }
        #
        # 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(transaction, 'id')
        amount = self.safe_number(transaction, 'quantity')
        address = self.safe_string(transaction, 'cryptoAddress')
        txid = self.safe_string(transaction, 'txId')
        updated = self.parse8601(self.safe_string(transaction, 'updatedAt'))
        opened = self.parse8601(self.safe_string(transaction, 'createdAt'))
        timestamp = opened if opened else updated
        type = 'deposit' if (opened is None) else 'withdrawal'
        currencyId = self.safe_string(transaction, 'currencySymbol')
        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:
            responseStatus = self.safe_string(transaction, 'status')
            if responseStatus == 'ERROR_INVALID_ADDRESS':
                status = 'failed'
            elif responseStatus == 'CANCELLED':
                status = 'canceled'
            elif responseStatus == 'PENDING':
                status = 'pending'
            elif responseStatus == 'COMPLETED':
                status = 'ok'
            elif responseStatus == 'AUTHORIZED' and (txid is not None):
                status = 'ok'
        feeCost = self.safe_number(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
        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_time_in_force(self, timeInForce):
        timeInForces = {
            'GOOD_TIL_CANCELLED': 'GTC',
            'IMMEDIATE_OR_CANCEL': 'IOC',
            'FILL_OR_KILL': 'FOK',
            'POST_ONLY_GOOD_TIL_CANCELLED': 'PO',
        }
        return self.safe_string(timeInForces, timeInForce, timeInForce)

    def parse_order(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')
        market = self.safe_market(marketSymbol, market, '-')
        symbol = market['symbol']
        feeCurrency = market['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_number(order, 'quantity')
        limit = self.safe_number(order, 'limit')
        fillQuantity = self.safe_number(order, 'fillQuantity')
        commission = self.safe_number(order, 'commission')
        proceeds = self.safe_number(order, 'proceeds')
        status = self.safe_string_lower(order, 'status')
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
        postOnly = (timeInForce == 'PO')
        return self.safe_order({
            'id': self.safe_string(order, 'id'),
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': direction,
            'price': limit,
            'stopPrice': None,
            'cost': proceeds,
            'average': None,
            'amount': quantity,
            'filled': fillQuantity,
            'remaining': None,
            'status': status,
            'fee': {
                'cost': commission,
                'currency': feeCurrency,
            },
            'info': order,
            'trades': None,
        })

    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 fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        response = None
        try:
            request = {
                'orderId': id,
            }
            response = self.privateGetOrdersOrderId(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
        return self.parse_order(response)

    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'),
            'type': self.safe_string(order, 'type'),
            'price': self.safe_number(order, 'average'),
            'amount': self.safe_number(order, 'filled'),
            'cost': self.safe_number(order, 'cost'),
            'symbol': self.safe_string(order, 'symbol'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': self.safe_value(order, 'fee'),
            'info': order,
            'takerOrMaker': None,
        }

    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

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        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 v1
            # 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 = self.privateGetOrdersClosed(self.extend(request, params))
        orders = self.parse_orders(response, market)
        trades = self.orders_to_trades(orders)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        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 v1
            # 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 = self.privateGetOrdersClosed(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    def create_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currencySymbol': currency['id'],
        }
        response = self.privatePostAddresses(self.extend(request, params))
        #
        #     {
        #         "status":"PROVISIONED",
        #         "currencySymbol":"XRP",
        #         "cryptoAddress":"rPVMhWBsfF9iMXYj3aAzJVkPDTFNSyWdKy",
        #         "cryptoAddressTag":"392034158"
        #     }
        #
        address = self.safe_string(response, 'cryptoAddress')
        message = self.safe_string(response, 'status')
        if not address or message == 'REQUESTED':
            raise AddressPending(self.id + ' the address for ' + code + ' is being generated(pending, not ready yet, retry again later)')
        tag = self.safe_string(response, 'cryptoAddressTag')
        if (tag is None) and (currency['type'] in self.options['tag']):
            tag = address
            address = currency['address']
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': response,
        }

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currencySymbol': currency['id'],
        }
        response = self.privateGetAddressesCurrencySymbol(self.extend(request, params))
        #
        #     {
        #         "status":"PROVISIONED",
        #         "currencySymbol":"XRP",
        #         "cryptoAddress":"rPVMhWBsfF9iMXYj3aAzJVkPDTFNSyWdKy",
        #         "cryptoAddressTag":"392034158"
        #     }
        #
        address = self.safe_string(response, 'cryptoAddress')
        message = self.safe_string(response, 'status')
        if not address or message == 'REQUESTED':
            raise AddressPending(self.id + ' the address for ' + code + ' is being generated(pending, not ready yet, retry again later)')
        tag = self.safe_string(response, 'cryptoAddressTag')
        if (tag is None) and (currency['type'] in self.options['tag']):
            tag = address
            address = currency['address']
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': response,
        }

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currencySymbol': currency['id'],
            'quantity': amount,
            'cryptoAddress': address,
        }
        if tag is not None:
            request['cryptoAddressTag'] = tag
        response = self.privatePostWithdrawals(self.extend(request, params))
        id = self.safe_string(response, 'id')
        return {
            'info': response,
            'id': id,
        }

    def sign(self, path, api='v3', method='GET', params={}, headers=None, body=None):
        url = self.implode_params(self.urls['api'][api], {
            'hostname': self.hostname,
        }) + '/'
        if api == 'private':
            url += self.version + '/'
            self.check_required_credentials()
            url += self.implode_params(path, params)
            params = self.omit(params, self.extract_params(path))
            hashString = ''
            if method == 'POST':
                body = self.json(params)
                hashString = body
            else:
                if params:
                    url += '?' + self.rawencode(params)
            contentHash = self.hash(self.encode(hashString), '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
            if method == 'POST':
                headers['Content-Type'] = 'application/json'
        else:
            if api == 'public':
                url += self.version + '/'
            url += self.implode_params(path, params)
            params = self.omit(params, self.extract_params(path))
            if params:
                url += '?' + self.urlencode(params)
        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] == '{':
            feedback = self.id + ' ' + body
            success = self.safe_value(response, 'success')
            if success is None:
                code = self.safe_string(response, 'code')
                if code is not None:
                    self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
                    self.throw_broadly_matched_exception(self.exceptions['broad'], code, feedback)
                if (code == 'NOT_FOUND') and (url.find('addresses') >= 0):
                    raise InvalidAddress(feedback)
                # raise ExchangeError(self.id + ' malformed response ' + self.json(response))
                return
            if isinstance(success, basestring):
                # bleutrade uses string instead of boolean
                success = (success == 'true')
            if not success:
                message = self.safe_string(response, 'message')
                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))
                self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
                if message is not None:
                    self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
                raise ExchangeError(feedback)
