# -*- 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
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class qtrade(Exchange):

    def describe(self):
        return self.deep_extend(super(qtrade, self).describe(), {
            'id': 'qtrade',
            'name': 'qTrade',
            'countries': ['US'],
            'rateLimit': 1000,
            'version': 'v1',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/80491487-74a99c00-896b-11ea-821e-d307e832f13e.jpg',
                'api': {
                    'rest': 'https://api.qtrade.io',
                },
                'www': 'https://qtrade.io',
                'doc': 'https://qtrade-exchange.github.io/qtrade-docs',
                'referral': 'https://qtrade.io/?ref=BKOQWVFGRH2C',
                'fees': 'https://qtrade.io/fees',
            },
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelOrder': True,
                'createMarketOrder': None,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': False,
                'createStopMarketOrder': False,
                'createStopOrder': False,
                'fetchBalance': True,
                'fetchBorrowRate': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': False,
                'fetchBorrowRatesPerSymbol': False,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDeposit': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchPosition': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': False,
                'fetchTransactions': None,
                'fetchTransfer': False,
                'fetchTransfers': True,
                'fetchWithdrawal': True,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': False,
                'withdraw': True,
            },
            'timeframes': {
                '5m': 'fivemin',
                '15m': 'fifteenmin',
                '30m': 'thirtymin',
                '1h': 'onehour',
                '2h': 'twohour',
                '4h': 'fourhour',
                '1d': 'oneday',
            },
            'api': {
                'public': {
                    'get': [
                        'ticker/{market_string}',
                        'tickers',
                        'currency/{code}',
                        'currencies',
                        'common',
                        'market/{market_string}',
                        'markets',
                        'market/{market_string}/trades',
                        'orderbook/{market_string}',
                        'market/{market_string}/ohlcv/{interval}',
                    ],
                },
                'private': {
                    'get': [
                        'me',
                        'balances',
                        'balances_all',  # undocumented
                        'market/{market_string}',
                        'orders',
                        'order/{order_id}',
                        'trades',
                        'withdraw/{withdraw_id}',
                        'withdraws',
                        'deposit/{deposit_id}',
                        'deposits',
                        'transfers',
                    ],
                    'post': [
                        'cancel_order',
                        'withdraw',
                        'deposit_address/{currency}',
                        'sell_limit',
                        'buy_limit',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'quote',
                    'tierBased': True,
                    'percentage': True,
                    'taker': self.parse_number('0.005'),
                    'maker': self.parse_number('0.0'),
                },
                'funding': {
                    'withdraw': {},
                },
            },
            'commonCurrencies': {
                'BTM': 'Bitmark',
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    'invalid_auth': AuthenticationError,
                    'insuff_funds': InsufficientFunds,
                    'market_not_found': BadSymbol,  # {"errors":[{"code":"market_not_found","title":"Requested market does not exist"}]}
                    'too_small': InvalidOrder,
                    'limit_exceeded': RateLimitExceeded,  # {"errors":[{"code":"limit_exceeded","title":"You have exceeded the windowed rate limit. Please see docs."}]}
                },
            },
        })

    async def fetch_markets(self, params={}):
        """
        retrieves data on all markets for qtrade
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        response = await self.publicGetMarkets(params)
        #
        #     {
        #         "data":{
        #             "markets":[
        #                 {
        #                     "id":5,
        #                     "market_currency":"BAC",
        #                     "base_currency":"BTC",
        #                     "maker_fee":"0.0025",
        #                     "taker_fee":"0.0025",
        #                     "metadata":{
        #                         "delisting_date":"7/15/2018",
        #                         "market_notices":[
        #                             {
        #                                 "message":"Delisting Notice: This market has been delisted due to low volume. Please cancel your orders and withdraw your funds by 7/15/2018.",
        #                                 "type":"warning"
        #                             }
        #                         ]
        #                     },
        #                     "can_trade":false,
        #                     "can_cancel":true,
        #                     "can_view":false,
        #                     "market_string":"BAC_BTC",
        #                     "minimum_sell_amount":"0.0001",
        #                     "minimum_buy_value":"0.0001",
        #                     "market_precision":8,  # note, they have reversed understanding of 'quote' vs 'base' concepts
        #                     "base_precision":8  # as noted in above comment
        #                 },
        #             ],
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        markets = self.safe_value(data, 'markets', [])
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            marketId = self.safe_string(market, 'market_string')
            numericId = self.safe_integer(market, 'id')
            baseId = self.safe_string(market, 'market_currency')
            quoteId = self.safe_string(market, 'base_currency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            canView = self.safe_value(market, 'can_view', False)
            canTrade = self.safe_value(market, 'can_trade', False)
            active = canTrade and canView
            result.append({
                'id': marketId,
                'numericId': numericId,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'active': active,
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': self.safe_number(market, 'taker_fee'),
                'maker': self.safe_number(market, 'maker_fee'),
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'market_precision'))),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'base_precision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(market, 'minimum_sell_value'),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_number(market, 'minimum_buy_value'),
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    async def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = await self.publicGetCurrencies(params)
        #
        #     {
        #         "data":{
        #             "currencies":[
        #                 {
        #                     "code":"DGB",
        #                     "long_name":"Digibyte",
        #                     "type":"bitcoin_like",
        #                     "precision":8,
        #                     "config":{
        #                         "price":0.0035,
        #                         "withdraw_fee":"10",
        #                         "deposit_types":[
        #                             {
        #                                 "label":"Address",
        #                                 "lookup_mode":"address",
        #                                 "render_type":"address",
        #                                 "deposit_type":"address",
        #                                 "lookup_config":{}
        #                             }
        #                         ],
        #                         "default_signer":103,
        #                         "address_version":30,
        #                         "satoshi_per_byte":300,
        #                         "required_confirmations":200,
        #                         "required_generate_confirmations":300
        #                     },
        #                     "metadata":{},
        #                     "minimum_order":"0.0001",
        #                     "status":"ok",
        #                     "can_withdraw":true,
        #                     "delisted":false,
        #                     "deposit_disabled":false,
        #                     "withdraw_disabled":false,
        #                     "deposit_warn_codes":[],
        #                     "withdraw_warn_codes":[]
        #                 },
        #             ],
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        currencies = self.safe_value(data, 'currencies', [])
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'code')
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'long_name')
            type = self.safe_string(currency, 'type')
            canWithdraw = self.safe_value(currency, 'can_withdraw', True)
            withdrawDisabled = self.safe_value(currency, 'withdraw_disabled', False)
            depositDisabled = self.safe_value(currency, 'deposit_disabled', False)
            deposit = not depositDisabled
            withdraw = canWithdraw and not withdrawDisabled
            config = self.safe_value(currency, 'config', {})
            status = self.safe_string(currency, 'status')
            active = withdraw and deposit and (status == 'ok')
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'type': type,
                'name': name,
                'fee': self.safe_number(config, 'withdraw_fee'),
                'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 'precision'))),
                'active': active,
                'deposit': deposit,
                'withdraw': withdraw,
                'limits': {
                    'amount': {
                        'min': self.safe_number(currency, 'minimum_order'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
            }
        return result

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "time":"2019-12-07T22:55:00Z",
        #         "open":"0.00197",
        #         "high":"0.00197",
        #         "low":"0.00197",
        #         "close":"0.00197",
        #         "volume":"0.00016676",
        #         "market_volume":"0.08465047"
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'time')),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'market_volume'),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='5m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_string': market['id'],
            'interval': self.timeframes[timeframe],
        }
        response = await self.publicGetMarketMarketStringOhlcvInterval(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "slices":[
        #                 {"time":"2019-12-07T22:55:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0.00016676","market_volume":"0.08465047"},
        #                 {"time":"2019-12-07T23:00:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"},
        #                 {"time":"2019-12-07T23:05:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"},
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        ohlcvs = self.safe_value(data, 'slices', [])
        return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {'market_string': market['id']}
        response = await self.publicGetOrderbookMarketString(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "buy":{
        #                 "0.00700015":"4.76196367",
        #                 "0.00700017":"1.89755391",
        #                 "0.00700018":"2.13214088",
        #             },
        #             "last_change":1588539869958811,
        #             "sell":{
        #                 "0.02418662":"0.19513696",
        #                 "0.02465627":"0.2439212",
        #                 "0.02530277":"0.663475931274359255",
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        orderbook = {}
        sides = {'buy': 'bids', 'sell': 'asks'}
        keys = list(sides.keys())
        for i in range(0, len(keys)):
            key = keys[i]
            side = sides[key]
            bidasks = self.safe_value(data, key, {})
            prices = list(bidasks.keys())
            result = []
            for j in range(0, len(prices)):
                priceAsString = prices[j]
                price = self.safe_number(prices, j)
                amount = self.safe_number(bidasks, priceAsString)
                result.append([price, amount])
            orderbook[side] = result
        timestamp = self.safe_integer_product(data, 'last_change', 0.001)
        return self.parse_order_book(orderbook, market['symbol'], timestamp)

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker, fetchTickers
        #
        #     {
        #         "ask":"0.02423119",
        #         "bid":"0.0230939",
        #         "day_avg_price":"0.0247031874349301",
        #         "day_change":"-0.0237543162270376",
        #         "day_high":"0.02470552",
        #         "day_low":"0.02470172",
        #         "day_open":"0.02530277",
        #         "day_volume_base":"0.00268074",
        #         "day_volume_market":"0.10851798",
        #         "id":41,
        #         "id_hr":"ETH_BTC",
        #         "last":"0.02470172",
        #         "last_change":1588533365354609
        #     }
        #
        marketId = self.safe_string(ticker, 'id_hr')
        symbol = self.safe_symbol(marketId, market, '_')
        timestamp = self.safe_integer_product(ticker, 'last_change', 0.001)
        previous = self.safe_string(ticker, 'day_open')
        last = self.safe_string(ticker, 'last')
        day_change = self.safe_string(ticker, 'day_change')
        average = self.safe_string(ticker, 'day_avg_price')
        baseVolume = self.safe_string(ticker, 'day_volume_market')
        quoteVolume = self.safe_string(ticker, 'day_volume_base')
        percentage = Precise.string_mul(day_change, '100')
        change = Precise.string_mul(day_change, previous)
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'day_high'),
            'low': self.safe_string(ticker, 'day_low'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': previous,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        :param [str]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        response = await self.publicGetTickers(params)
        #
        #     {
        #         "data":{
        #             "markets":[
        #                 {
        #                     "ask":"0.0000003",
        #                     "bid":"0.00000029",
        #                     "day_avg_price":"0.0000002999979728",
        #                     "day_change":"0.0344827586206897",
        #                     "day_high":"0.0000003",
        #                     "day_low":"0.0000003",
        #                     "day_open":"0.00000029",
        #                     "day_volume_base":"0.00591958",
        #                     "day_volume_market":"19732.06666665",
        #                     "id":36,
        #                     "id_hr":"DOGE_BTC",
        #                     "last":"0.0000003",
        #                     "last_change":1588534202130778
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        tickers = self.safe_value(data, 'markets', [])
        result = {}
        for i in range(0, len(tickers)):
            ticker = self.parse_ticker(tickers[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array(result, 'symbol', symbols)

    async def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_string': market['id'],
        }
        response = await self.publicGetTickerMarketString(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "ask":"0.02423119",
        #             "bid":"0.0230939",
        #             "day_avg_price":"0.0247031874349301",
        #             "day_change":"-0.0237543162270376",
        #             "day_high":"0.02470552",
        #             "day_low":"0.02470172",
        #             "day_open":"0.02530277",
        #             "day_volume_base":"0.00268074",
        #             "day_volume_market":"0.10851798",
        #             "id":41,
        #             "id_hr":"ETH_BTC",
        #             "last":"0.02470172",
        #             "last_change":1588533365354609
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.parse_ticker(data, market)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_string': market['id'],
            # 'older_than': 123,  # returns trades with id < older_than
            # 'newer_than': 123,  # returns trades with id > newer_than
        }
        response = await self.publicGetMarketMarketStringTrades(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "trades":[
        #                 {
        #                     "id":85507,
        #                     "amount":"0.09390502",
        #                     "price":"0.02556325",
        #                     "base_volume":"0.00240051",
        #                     "seller_taker":true,
        #                     "side":"sell",
        #                     "created_at":"0001-01-01T00:00:00Z",
        #                     "created_at_ts":1581560391338718
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        trades = self.safe_value(data, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all trades made by the user
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        await self.load_markets()
        request = {
            'desc': True,  # Returns newest trades first when True
            # 'older_than': 123,  # returns trades with id < older_than
            # 'newer_than': 123,  # returns trades with id > newer_than
        }
        market = None
        numericId = self.safe_value(params, 'market_id')
        if numericId is not None:
            request['market_id'] = numericId  # mutually exclusive with market_string
        elif symbol is not None:
            market = self.market(symbol)
            request['market_string'] = market['id']
        response = await self.privateGetTrades(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "trades":[
        #                 {
        #                     "id":107331,
        #                     "market_amount":"0.1082536946986",
        #                     "price":"0.0230939",
        #                     "base_amount":"0.00249999",
        #                     "order_id":13790596,
        #                     "market_id":41,
        #                     "market_string":"ETH_BTC",
        #                     "taker":true,
        #                     "base_fee":"0.00001249",
        #                     "side":"sell",
        #                     "created_at":"2020-05-04T06:08:18.513413Z"
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        trades = self.safe_value(data, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "id":85507,
        #         "amount":"0.09390502",
        #         "price":"0.02556325",
        #         "base_volume":"0.00240051",
        #         "seller_taker":true,
        #         "side":"sell",
        #         "created_at":"0001-01-01T00:00:00Z",
        #         "created_at_ts":1581560391338718
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "id":107331,
        #         "market_amount":"0.1082536946986",
        #         "price":"0.0230939",
        #         "base_amount":"0.00249999",
        #         "order_id":13790596,
        #         "market_id":41,
        #         "market_string":"ETH_BTC",
        #         "taker":true,
        #         "base_fee":"0.00001249",
        #         "side":"sell",
        #         "created_at":"2020-05-04T06:08:18.513413Z"
        #     }
        #
        # createOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "base_amount": "9.58970687",
        #         "base_fee": "0.02397426",
        #         "created_at": "0001-01-01T00:00:00Z",
        #         "id": 0,
        #         "market_amount": "0.97179355",
        #         "price": "9.86804952",
        #         "taker": True
        #     }
        #
        id = self.safe_string(trade, 'id')
        timestamp = self.safe_integer_product(trade, 'created_at_ts', 0.001)
        if timestamp is None:
            timestamp = self.parse8601(self.safe_string(trade, 'created_at'))
        side = self.safe_string(trade, 'side')
        marketId = self.safe_string(trade, 'market_string')
        market = self.safe_market(marketId, market)
        cost = self.safe_string_2(trade, 'base_volume', 'base_amount')
        price = self.safe_string(trade, 'price')
        amount = self.safe_string_2(trade, 'market_amount', 'amount')
        fee = None
        feeCost = self.safe_string(trade, 'base_fee')
        if feeCost is not None:
            feeCurrencyCode = None if (market is None) else market['quote']
            fee = {
                'currency': feeCurrencyCode,
                'cost': feeCost,
            }
        taker = self.safe_value(trade, 'taker', True)
        takerOrMaker = 'taker' if taker else 'maker'
        orderId = self.safe_string(trade, 'order_id')
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }, market)

    async def fetch_trading_fee(self, symbol, params={}):
        """
        fetch the trading fees for a market
        :param str symbol: unified market symbol
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: a `fee structure <https://docs.ccxt.com/en/latest/manual.html#fee-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_string': market['id'],
        }
        response = await self.publicGetMarketMarketString(self.extend(request, params))
        #
        #     {
        #         data: {
        #             market: {
        #                 id: '41',
        #                 market_currency: 'ETH',
        #                 base_currency: 'BTC',
        #                 maker_fee: '0',
        #                 taker_fee: '0.005',
        #                 metadata: {},
        #                 can_trade: True,
        #                 can_cancel: True,
        #                 can_view: True,
        #                 market_string: 'ETH_BTC',
        #                 minimum_sell_amount: '0.001',
        #                 minimum_buy_value: '0.0001',
        #                 market_precision: '18',
        #                 base_precision: '8'
        #             },
        #             recent_trades: []
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        marketData = self.safe_value(data, 'market', {})
        return {
            'info': marketData,
            'symbol': market['symbol'],
            'maker': self.safe_number(marketData, 'maker_fee'),
            'taker': self.safe_number(marketData, 'taker_fee'),
            'percentage': True,
            'tierBased': True,
        }

    def parse_balance(self, response):
        data = self.safe_value(response, 'data', {})
        balances = self.safe_value(data, 'balances', [])
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = result[code] if (code in result) else self.account()
            account['free'] = self.safe_string(balance, 'balance')
            account['used'] = '0'
            result[code] = account
        balances = self.safe_value(data, 'order_balances', [])
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = result[code] if (code in result) else self.account()
            account['used'] = self.safe_string(balance, 'balance')
            result[code] = account
        return self.safe_balance(result)

    async def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        await self.load_markets()
        response = await self.privateGetBalancesAll(params)
        #
        #     {
        #         "data":{
        #             "balances": [
        #                 {"balance": "100000000", "currency": "BCH"},
        #                 {"balance": "99992435.78253015", "currency": "LTC"},
        #                 {"balance": "99927153.76074182", "currency": "BTC"},
        #             ],
        #             "order_balances":[],
        #             "limit_used":0,
        #             "limit_remaining":4000,
        #             "limit":4000
        #         }
        #     }
        #
        return self.parse_balance(response)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float|None price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if type != 'limit':
            raise InvalidOrder(self.id + ' createOrder() allows limit orders only')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'amount': self.amount_to_precision(market['symbol'], amount),
            'market_id': market['numericId'],
            'price': self.price_to_precision(market['symbol'], price),
        }
        method = 'privatePostSellLimit' if (side == 'sell') else 'privatePostBuyLimit'
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "data": {
        #             "order": {
        #                 "created_at": "2018-04-06T20:46:52.899248Z",
        #                 "id": 13253,
        #                 "market_amount": "1",
        #                 "market_amount_remaining": "0",
        #                 "market_id": 1,
        #                 "open": False,
        #                 "order_type": "sell_limit",
        #                 "price": "0.01",
        #                 "trades": [
        #                     {
        #                         "base_amount": "0.27834267",
        #                         "base_fee": "0.00069585",
        #                         "created_at": "0001-01-01T00:00:00Z",
        #                         "id": 0,
        #                         "market_amount": "0.02820645",
        #                         "price": "9.86805058",
        #                         "taker": True
        #                     },
        #                     {
        #                         "base_amount": "9.58970687",
        #                         "base_fee": "0.02397426",
        #                         "created_at": "0001-01-01T00:00:00Z",
        #                         "id": 0,
        #                         "market_amount": "0.97179355",
        #                         "price": "9.86804952",
        #                         "taker": True
        #                     }
        #                 ]
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        order = self.safe_value(data, 'order', {})
        return self.parse_order(order, market)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "created_at": "2018-04-06T20:46:52.899248Z",
        #         "id": 13253,
        #         "market_amount": "1",
        #         "market_amount_remaining": "0",
        #         "market_id": 1,
        #         "open": False,
        #         "order_type": "sell_limit",
        #         "price": "0.01",
        #         "trades": [
        #             {
        #                 "base_amount": "0.27834267",
        #                 "base_fee": "0.00069585",
        #                 "created_at": "0001-01-01T00:00:00Z",
        #                 "id": 0,
        #                 "market_amount": "0.02820645",
        #                 "price": "9.86805058",
        #                 "taker": True
        #             },
        #             {
        #                 "base_amount": "9.58970687",
        #                 "base_fee": "0.02397426",
        #                 "created_at": "0001-01-01T00:00:00Z",
        #                 "id": 0,
        #                 "market_amount": "0.97179355",
        #                 "price": "9.86804952",
        #                 "taker": True
        #             }
        #         ]
        #     }
        #
        # fetchOrder
        #
        #     {
        #         id: 13790596,
        #         market_amount: "0.15",
        #         market_amount_remaining: "0",
        #         created_at: "2020-05-04T06:08:18.513413Z",
        #         price: "0.0230939",
        #         base_amount: "0",
        #         order_type: "sell_limit",
        #         market_id: 41,
        #         market_string: "ETH_BTC",
        #         open: False,
        #         trades: [
        #             {
        #                 id: 107331,
        #                 market_amount: "0.1082536946986",
        #                 price: "0.0230939",
        #                 base_amount: "0.00249999",
        #                 taker: True,
        #                 base_fee: "0.00001249",
        #                 created_at: "2020-05-04T06:08:18.513413Z",
        #             }
        #         ],
        #         close_reason: "canceled"
        #     }
        #
        id = self.safe_string(order, 'id')
        timestamp = self.parse8601(self.safe_string(order, 'created_at'))
        sideType = self.safe_string(order, 'order_type')
        orderType = None
        side = None
        if sideType is not None:
            parts = sideType.split('_')
            side = self.safe_string(parts, 0)
            orderType = self.safe_string(parts, 1)
        price = self.safe_string(order, 'price')
        amount = self.safe_string(order, 'market_amount')
        remaining = self.safe_string(order, 'market_amount_remaining')
        open = self.safe_value(order, 'open', False)
        closeReason = self.safe_string(order, 'close_reason')
        status = None
        if open:
            status = 'open'
        elif closeReason == 'canceled':
            status = 'canceled'
        else:
            status = 'closed'
        marketId = self.safe_string(order, 'market_string')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        rawTrades = self.safe_value(order, 'trades', [])
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': orderType,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'average': None,
            'amount': amount,
            'remaining': remaining,
            'filled': None,
            'status': status,
            'fee': None,
            'fees': None,
            'cost': None,
            'trades': rawTrades,
        }, market)

    async def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str|None symbol: not used by qtrade cancelOrder()
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        request = {
            'id': int(id),
        }
        # successful cancellation returns 200 with no payload
        return await self.privatePostCancelOrder(self.extend(request, params))

    async def fetch_order(self, id, symbol=None, params={}):
        """
        fetches information on an order made by the user
        :param str|None symbol: not used by qtrade fetchOrder
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        request = {'order_id': id}
        response = await self.privateGetOrderOrderId(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "order":{
        #                 "id":13790596,
        #                 "market_amount":"0.15",
        #                 "market_amount_remaining":"0.0417463053014",
        #                 "created_at":"2020-05-04T06:08:18.513413Z",
        #                 "price":"0.0230939",
        #                 "order_type":"sell_limit",
        #                 "market_id":41,
        #                 "market_string":"ETH_BTC",
        #                 "open":true,
        #                 "trades":[
        #                     {
        #                         "id":107331,
        #                         "market_amount":"0.1082536946986",
        #                         "price":"0.0230939",
        #                         "base_amount":"0.00249999",
        #                         "taker":true,
        #                         "base_fee":"0.00001249",
        #                         "created_at":"2020-05-04T06:08:18.513413Z"
        #                     }
        #                 ]
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        order = self.safe_value(data, 'order', {})
        return self.parse_order(order)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        request = {
            # 'open': True,
            # 'older_than': 123,  # returns orders with id < older_than
            # 'newer_than': 123,  # returns orders with id > newer_than
        }
        market = None
        numericId = self.safe_value(params, 'market_id')
        if numericId is not None:
            request['market_id'] = numericId  # mutually exclusive with market_string
        elif symbol is not None:
            market = self.market(symbol)
            request['market_string'] = market['id']
        response = await self.privateGetOrders(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "orders":[
        #                 {
        #                     "id":13790596,
        #                     "market_amount":"0.15",
        #                     "market_amount_remaining":"0.0417463053014",
        #                     "created_at":"2020-05-04T06:08:18.513413Z",
        #                     "price":"0.0230939",
        #                     "order_type":"sell_limit",
        #                     "market_id":41,
        #                     "market_string":"ETH_BTC",
        #                     "open":true,
        #                     "trades":[
        #                         {
        #                             "id":107331,
        #                             "market_amount":"0.1082536946986",
        #                             "price":"0.0230939",
        #                             "base_amount":"0.00249999",
        #                             "taker":true,
        #                             "base_fee":"0.00001249",
        #                             "created_at":"2020-05-04T06:08:18.513413Z"
        #                         }
        #                     ]
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        orders = self.safe_value(data, 'orders', [])
        return self.parse_orders(orders, market, since, limit)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch open orders for
        :param int|None limit: the maximum number of  open orders structures to retrieve
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        request = {'open': True}
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple closed orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        request = {'open': False}
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     {
        #         "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #         "currency_status":"ok",
        #         "deposit_methods":{
        #             "address":{
        #                 "deposit_type":"address",
        #                 "render_type":"address",
        #                 "label":"Address",
        #                 "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #             },
        #         },
        #     }
        #
        code = None if (currency is None) else currency['code']
        address = self.safe_string(depositAddress, 'address')
        tag = None
        if address is not None:
            parts = address.split(':')
            address = self.safe_string(parts, 0)
            tag = self.safe_string(parts, 1)
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': None,
            'info': depositAddress,
        }

    async def fetch_deposit_address(self, code, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await self.privatePostDepositAddressCurrency(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #             "currency_status":"ok",
        #             "deposit_methods":{
        #                 "address":{
        #                     "deposit_type":"address",
        #                     "render_type":"address",
        #                     "label":"Address",
        #                     "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                 },
        #             },
        #         },
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.parse_deposit_address(data, currency)

    async def fetch_deposit(self, id, code=None, params={}):
        """
        fetch information on a deposit
        :param str id: deposit id
        :param str|None code: not used by qtrade fetchDeposit()
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        await self.load_markets()
        request = {
            'deposit_id': id,
        }
        response = await self.privateGetDepositDepositId(self.extend(request, params))
        #
        #     {
        #         "data":{
        #             "deposit":{
        #                 "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        #                 "amount":"0.13",
        #                 "currency":"ETH",
        #                 "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                 "status":"credited",
        #                 "relay_status":"",
        #                 "network_data":{
        #                     "confirms":87,
        #                     "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        #                     "sweep_balance":"0.150000000000000000",
        #                     "confirms_required":80,
        #                     "unsigned_sweep_tx":{
        #                         "chainId":1,
        #                         "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                         "gas":"0x5208",
        #                         "gasPrice":"0x19b45a500",
        #                         "nonce":"0x0",
        #                         "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        #                         "value":"0x214646b6347d800"
        #                     },
        #                     "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        #                     "tx_index":"0x6f",
        #                     "tx_value":"0.130000000000000000",
        #                     "key_index":311,
        #                     "blockheight":9877869,
        #                     "signed_sweep_tx":{
        #                         "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        #                         "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
        #                     },
        #                     "estimated_sweep_tx_fee":144900000000000
        #                 },
        #                 "created_at":"2020-05-04T05:38:42.145162Z"
        #             }
        #         }
        #     }
        data = self.safe_value(response, 'data', {})
        deposit = self.safe_value(data, 'deposit', {})
        return self.parse_transaction(deposit)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        """
        fetch all deposits made to an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch deposits for
        :param int|None limit: the maximum number of deposits structures to retrieve
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        await self.load_markets()
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = await self.privateGetDeposits(params)
        #
        #     {
        #         "data":{
        #             "deposits":[
        #                 {
        #                     "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        #                     "amount":"0.13",
        #                     "currency":"ETH",
        #                     "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                     "status":"credited",
        #                     "relay_status":"",
        #                     "network_data":{
        #                         "confirms":87,
        #                         "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        #                         "sweep_balance":"0.150000000000000000",
        #                         "confirms_required":80,
        #                         "unsigned_sweep_tx":{
        #                             "chainId":1,
        #                             "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                             "gas":"0x5208",
        #                             "gasPrice":"0x19b45a500",
        #                             "nonce":"0x0",
        #                             "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        #                             "value":"0x214646b6347d800"
        #                         },
        #                         "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        #                         "tx_index":"0x6f",
        #                         "tx_value":"0.130000000000000000",
        #                         "key_index":311,
        #                         "blockheight":9877869,
        #                         "signed_sweep_tx":{
        #                             "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        #                             "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
        #                         },
        #                         "estimated_sweep_tx_fee":144900000000000
        #                     },
        #                     "created_at":"2020-05-04T05:38:42.145162Z"
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        deposits = self.safe_value(data, 'deposits', [])
        return self.parse_transactions(deposits, currency, since, limit)

    async def fetch_transfers(self, code=None, since=None, limit=None, params={}):
        """
        fetch a history of internal transfers made on an account
        :param str|None code: unified currency code of the currency transferred
        :param int|None since: the earliest time in ms to fetch transfers for
        :param int|None limit: the maximum number of  transfers structures to retrieve
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `transfer structures <https://docs.ccxt.com/en/latest/manual.html#transfer-structure>`
        """
        await self.load_markets()
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = await self.privateGetTransfers(params)
        #
        #     {
        #         "data": {
        #             "transfers": [
        #                 {
        #                     "amount": "0.5",
        #                     "created_at": "2018-12-10T00:06:41.066665Z",
        #                     "currency": "BTC",
        #                     "id": 9,
        #                     "reason_code": "referral_payout",
        #                     "reason_metadata": {
        #                         "note": "January referral earnings"
        #                     },
        #                     "sender_email": "qtrade",
        #                     "sender_id": 218
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        transfers = self.safe_value(data, 'transfers', [])
        return self.parse_transfers(transfers, currency, since, limit)

    def parse_transfer(self, transfer, currency=None):
        #
        #     {
        #         "amount": "0.5",
        #         "created_at": "2018-12-10T00:06:41.066665Z",
        #         "currency": "BTC",
        #         "id": 9,
        #         "reason_code": "referral_payout",
        #         "reason_metadata": {
        #             "note": "January referral earnings"
        #         },
        #         "sender_email": "qtrade",
        #         "sender_id": 218
        #     }
        #
        currencyId = self.safe_string(transfer, 'currency')
        dateTime = self.safe_string(transfer, 'created_at')
        return {
            'info': transfer,
            'id': self.safe_string(transfer, 'id'),
            'timestamp': self.parse8601(dateTime),
            'datetime': dateTime,
            'currency': self.safe_currency_code(currencyId, currency),
            'amount': self.safe_number(transfer, 'amount'),
            'fromAccount': self.safe_string(transfer, 'sender_id'),
            'toAccount': None,
            'status': 'ok',
        }

    async def fetch_withdrawal(self, id, code=None, params={}):
        """
        fetch data on a currency withdrawal via the withdrawal id
        :param str id: withdrawal id
        :param str|None code: not used by qtrade.fetchWithdrawal
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        await self.load_markets()
        request = {
            'withdraw_id': id,
        }
        response = await self.privateGetWithdrawWithdrawId(self.extend(request, params))
        #
        #     {
        #         data: {
        #             withdraw: {
        #                 "id":25524,
        #                 "amount":"0.0417463053014",
        #                 "user_id":0,
        #                 "currency":"ETH",
        #                 "network_data":{
        #                     "unsigned_tx":{
        #                         "chainId":1,
        #                         "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        #                         "gas":"0x5208",
        #                         "gasPrice":"0x20c8558e9",
        #                         "nonce":"0xf3",
        #                         "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                         "value":"0x71712bcd113308"
        #                     },
        #                     "estimated_tx_fee":184800004893000,
        #                     "confirms_required":80,
        #                     "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        #                     "confirms":83,
        #                     "signed_tx":{
        #                         "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        #                         "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
        #                     }
        #                 },
        #                 "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                 "status":"confirmed",
        #                 "relay_status":"",
        #                 "created_at":"2020-05-05T06:32:19.907061Z",
        #                 "cancel_requested":false
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        withdrawal = self.safe_value(data, 'withdraw', {})
        return self.parse_transaction(withdrawal)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        """
        fetch all withdrawals made from an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch withdrawals for
        :param int|None limit: the maximum number of withdrawals structures to retrieve
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        await self.load_markets()
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = await self.privateGetWithdraws(params)
        #     {
        #         "data":{
        #             "withdraws":[
        #                 {
        #                     "id":25524,
        #                     "amount":"0.0417463053014",
        #                     "user_id":0,
        #                     "currency":"ETH",
        #                     "network_data":{
        #                         "unsigned_tx":{
        #                             "chainId":1,
        #                             "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        #                             "gas":"0x5208",
        #                             "gasPrice":"0x20c8558e9",
        #                             "nonce":"0xf3",
        #                             "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                             "value":"0x71712bcd113308"
        #                         },
        #                         "estimated_tx_fee":184800004893000,
        #                         "confirms_required":80,
        #                         "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        #                         "confirms":83,
        #                         "signed_tx":{
        #                             "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        #                             "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
        #                         }
        #                     },
        #                     "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                     "status":"confirmed",
        #                     "relay_status":"",
        #                     "created_at":"2020-05-05T06:32:19.907061Z",
        #                     "cancel_requested":false
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        withdrawals = self.safe_value(data, 'withdraws', [])
        return self.parse_transactions(withdrawals, currency, since, limit)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits, fetchDeposit
        #
        #     {
        #         "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        #         "amount":"0.13",
        #         "currency":"ETH",
        #         "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #         "status":"credited",
        #         "relay_status":"",
        #         "network_data":{
        #             "confirms":87,
        #             "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        #             "sweep_balance":"0.150000000000000000",
        #             "confirms_required":80,
        #             "unsigned_sweep_tx":{
        #                 "chainId":1,
        #                 "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                 "gas":"0x5208",
        #                 "gasPrice":"0x19b45a500",
        #                 "nonce":"0x0",
        #                 "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        #                 "value":"0x214646b6347d800"
        #             },
        #             "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        #             "tx_index":"0x6f",
        #             "tx_value":"0.130000000000000000",
        #             "key_index":311,
        #             "blockheight":9877869,
        #             "signed_sweep_tx":{
        #                 "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        #                 "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
        #             },
        #             "estimated_sweep_tx_fee":144900000000000
        #         },
        #         "created_at":"2020-05-04T05:38:42.145162Z"
        #     }
        #
        # fetchWithdrawals, fetchWithdrawal
        #
        #     {
        #         "id":25524,
        #         "amount":"0.0417463053014",
        #         "user_id":0,
        #         "currency":"ETH",
        #         "network_data":{
        #             "unsigned_tx":{
        #                 "chainId":1,
        #                 "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        #                 "gas":"0x5208",
        #                 "gasPrice":"0x20c8558e9",
        #                 "nonce":"0xf3",
        #                 "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #                 "value":"0x71712bcd113308"
        #             },
        #             "estimated_tx_fee":184800004893000,
        #             "confirms_required":80,
        #             "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        #             "confirms":83,
        #             "signed_tx":{
        #                 "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        #                 "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
        #             }
        #         },
        #         "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        #         "status":"confirmed",
        #         "relay_status":"",
        #         "created_at":"2020-05-05T06:32:19.907061Z",
        #         "cancel_requested":false
        #     }
        #
        # withdraw
        #
        #     {
        #         "code": "initiated",
        #         "id": 3,
        #         "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process."
        #     }
        #
        timestamp = self.parse8601(self.safe_string(transaction, 'created_at'))
        id = self.safe_string(transaction, 'id')
        networkData = self.safe_value(transaction, 'network_data', {})
        unsignedTx = self.safe_value(networkData, 'unsigned_tx', {})
        addressFrom = self.safe_string(unsignedTx, 'from')
        txid = self.safe_string(networkData, 'txid')
        address = self.safe_string(transaction, 'address')
        tag = None
        if address is not None:
            parts = address.split(':')
            numParts = len(parts)
            if numParts > 1:
                address = self.safe_string(parts, 0)
                tag = self.safe_string(parts, 1)
        addressTo = address
        tagFrom = None
        tagTo = tag
        cancelRequested = self.safe_value(transaction, 'cancel_requested')
        type = 'deposit' if (cancelRequested is None) else 'withdrawal'
        amount = self.safe_number(transaction, 'amount')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        statusCode = self.safe_string(transaction, 'code')
        if cancelRequested:
            status = 'canceled'
        elif status is None:
            status = self.parse_transaction_status(statusCode)
        fee = None
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'addressFrom': addressFrom,
            'addressTo': addressTo,
            'address': address,
            'tagFrom': tagFrom,
            'tagTo': tagTo,
            'tag': tag,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'fee': fee,
        }

    def parse_transaction_status(self, status):
        statuses = {
            'initiated': 'pending',
            'needs_create': 'pending',
            'credited': 'ok',
            'confirmed': 'ok',
        }
        return self.safe_string(statuses, status, status)

    async def withdraw(self, code, amount, address, tag=None, params={}):
        """
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str|None tag:
        :param dict params: extra parameters specific to the qtrade api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'address': address,
            'amount': amount,
            'currency': currency['id'],
        }
        if tag is not None:
            request['address'] += ':' + tag
        response = await self.privatePostWithdraw(self.extend(request, params))
        #
        #     {
        #         "data": {
        #             "code": "initiated",
        #             "id": 3,
        #             "result": "Withdraw initiated. Please allow 3-5 minutes for our system to process."
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        result = self.parse_transaction(data)
        return self.extend(result, {
            'currency': code,
            'address': address,
            'addressTo': address,
            'tag': tag,
            'tagTo': tag,
            'amount': amount,
        })

    def nonce(self):
        return self.milliseconds()

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = '/' + self.version + '/'
        if api == 'private':
            url += 'user/'
        url += self.implode_params(path, params)
        request = self.omit(params, self.extract_params(path))
        if method == 'POST':
            body = self.json(request)
        else:
            if request:
                url += '?' + self.urlencode(request)
        if api == 'private':
            timestamp = str(self.milliseconds())
            bodyAsString = body if (method == 'POST') else ''
            auth = "\n".join([
                method,
                url,
                timestamp,
                bodyAsString,
                self.secret,
            ])  # eslint-disable-line quotes
            hash = self.hash(self.encode(auth), 'sha256', 'base64')
            key = self.apiKey
            if not isinstance(key, str):
                key = str(key)
            signature = 'HMAC-SHA256 ' + key + ':' + hash
            headers = {
                'Authorization': signature,
                'HMAC-Timestamp': timestamp,
            }
            if method == 'POST':
                headers['Content-Type'] = 'application/json'
        url = self.urls['api']['rest'] + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        #
        #     {"errors":[{"code":"insuff_funds","title":"Your available balance is too low for that action"}]}
        #     {"errors":[{"code": "invalid_auth","title": "Invalid HMAC signature"}]}
        #
        if response is None:
            return
        errors = self.safe_value(response, 'errors', [])
        numErrors = len(errors)
        if numErrors < 1:
            return
        feedback = self.id + ' ' + body
        for i in range(0, len(errors)):
            error = errors[i]
            errorCode = self.safe_string(error, 'code')
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
        raise ExchangeError(feedback)  # unknown message
