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


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': True,
            '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)

    async def fetch_markets(self, params={}):
        response = await 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_float(market, 'minTradeSize'),
                        'max': None,
                    },
                    'price': {
                        'min': 1 / math.pow(10, precision['price']),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
            })
        return result

    async def fetch_balance(self, params={}):
        await self.load_markets()
        balances = await 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_float(balance, 'available')
            account['total'] = self.safe_float(balance, 'total')
            result[code] = account
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await 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 = await 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, None, 'bid', 'ask', 'rate', 'quantity')
        orderbook['nonce'] = sequence
        return orderbook

    async def fetch_currencies(self, params={}):
        response = await 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_float(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,
                    },
                    'price': {
                        'min': 1 / math.pow(10, precision),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        '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_float(ticker, 'percentChange')
        last = self.safe_float(ticker, 'lastTradeRate')
        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, 'bidRate'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'askRate'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': self.safe_float(ticker, 'quoteVolume'),
            'info': ticker,
        }

    async def fetch_tickers(self, symbols=None, params={}):
        await 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 = await 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)

    async def fetch_ticker(self, symbol, params={}):
        await 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 = await 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, '-')
        cost = None
        price = self.safe_float(trade, 'rate')
        amount = self.safe_float(trade, 'quantity')
        if amount is not None:
            if price is not None:
                cost = price * amount
        takerOrMaker = None
        isTaker = self.safe_value(trade, 'isTaker')
        if isTaker is not None:
            takerOrMaker = 'taker' if isTaker else 'maker'
        fee = None
        feeCost = self.safe_float(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,
        }

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

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'marketSymbol': self.market_id(symbol),
        }
        response = await 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_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'volume'),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await 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 = await 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)

    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['marketSymbol'] = market['id']
        response = await self.privateGetOrdersOpen(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    async def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            'orderId': id,
        }
        response = await 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)

    async 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)
        await 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_float_2(params, 'ceiling', 'cost', amount)
            elif isCeilingMarket:
                cost = self.safe_float_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 = await 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)

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

    async def cancel_all_orders(self, symbol=None, params={}):
        await self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['marketSymbol'] = market['id']
        response = await 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)

    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['currencySymbol'] = currency['id']
        response = await 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)

    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['currencySymbol'] = currency['id']
        response = await 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_float(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_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
        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')
        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')
        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
        status = self.safe_string_lower(order, 'status')
        if (status == 'closed') and (remaining is not None) and (remaining > 0):
            status = 'canceled'
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
        postOnly = (timeInForce == 'PO')
        return {
            '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': average,
            'amount': quantity,
            'filled': fillQuantity,
            'remaining': remaining,
            '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)

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

    async def fetch_my_trades(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 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 = await 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)

    async def fetch_closed_orders(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 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 = await self.privateGetOrdersClosed(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    async def create_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currencySymbol': currency['id'],
        }
        response = await 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,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currencySymbol': currency['id'],
        }
        response = await 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,
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currencySymbol': currency['id'],
            'quantity': amount,
            'cryptoAddress': address,
        }
        if tag is not None:
            request['cryptoAddressTag'] = tag
        response = await 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)
