# -*- 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
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadSymbol
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class eqonex(Exchange):

    def describe(self):
        return self.deep_extend(super(eqonex, self).describe(), {
            'id': 'eqonex',
            'name': 'EQONEX',
            'countries': ['US', 'SG'],  # United States, Singapore
            'rateLimit': 10,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': None,  # has but not fully implemented
                'future': None,  # has but not fully implemented
                'option': False,
                'cancelOrder': True,
                'createOrder': True,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchBorrowRate': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': False,
                'fetchBorrowRatesPerSymbol': False,
                'fetchCanceledOrders': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchPositionMode': False,
                'fetchTicker': None,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTradingLimits': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': 1,
                '5m': 2,
                '15m': 3,
                '1h': 4,
                '6h': 5,
                '1d': 6,
                '7d': 7,
                '1w': 7,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/122649755-1a076c80-d138-11eb-8f2e-9a9166a03d79.jpg',
                'test': {
                    'public': 'https://testnet.eqonex.com/api',
                    'private': 'https://testnet.eqonex.com/api',
                },
                'api': {
                    'public': 'https://eqonex.com/api',
                    'private': 'https://eqonex.com/api',
                },
                'www': 'https://eqonex.com',
                'doc': [
                    'https://developer.eqonex.com',
                ],
                'referral': 'https://eqonex.com?referredByCode=zpa8kij4ouvBFup3',
            },
            'api': {
                'public': {
                    'get': [
                        'health',
                        'getInstruments',
                        'getInstrumentPairs',
                        'getOrderBook',
                        'getRisk',
                        'getTradeHistory',
                        'getFundingRateHistory',
                        'getChart',
                        'getExchangeInfo',  # not documented
                    ],
                },
                'private': {
                    'post': [
                        'logon',
                        'order',
                        'cancelOrder',
                        'cancelReplaceOrder',
                        'getOrder',
                        'getOrders',
                        'getOrderStatus',
                        'getOrderHistory',
                        'userTrades',
                        'getPositions',
                        'cancelAll',
                        'getUserHistory',
                        'getRisk',
                        'getDepositAddresses',
                        'getDepositHistory',  # not documented
                        'getWithdrawRequests',
                        'sendWithdrawRequest',
                        'getTransferHistory',
                    ],
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'uid': True,
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'broad': {
                    'symbol not found': BadSymbol,
                },
            },
        })

    async def fetch_markets(self, params={}):
        """
        retrieves data on all markets for eqonex
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        request = {
            'verbose': True,
        }
        response = await self.publicGetGetInstrumentPairs(self.extend(request, params))
        #
        #    {
        #        "instrumentPairs": [
        #            {
        #                "instrumentId":303,
        #                "symbol":"BTC/USDC[220325]",
        #                "quoteId":1,
        #                "baseId":3,
        #                "price_scale":2,
        #                "quantity_scale":6,
        #                "securityStatus":1,
        #                "securityDesc":"BTC Dated Future",
        #                "assetType":"DATED_FUTURE",
        #                "currency":"BTC",
        #                "contAmtCurr":"USDC",
        #                "settlCurrency":"USDC",
        #                "commCurrency":"USDC",
        #                "cfiCode":"FFCPSX",
        #                "securityExchange":"EQOS",
        #                "micCode":"EQOD",
        #                "instrumentPricePrecision":2,
        #                "minPriceIncrement":1.0,
        #                "minPriceIncrementAmount":1.0,
        #                "roundLot":100,
        #                "minTradeVol":0.000100,
        #                "maxTradeVol":0.000000,
        #                "qtyType":0,
        #                "contractMultiplier":1.0,
        #                "auctionStartTime":0,
        #                "auctionDuration":0,
        #                "auctionFrequency":0,
        #                "auctionPrice":0,
        #                "auctionVolume":0,
        #                "marketStatus":"OPEN",
        #                "underlyingSymbol":"BTC/USDC",
        #                "underlyingSecurityId":52,
        #                "underlyingSecuritySource":"M",
        #                "underlyingSecurityExchange":"EQOC",
        #                "issueDate":1643256000000,
        #                "maturityDate":"2022-03-25",
        #                "maturityTime":"2022-03-25T08:00:00Z",
        #                "contractExpireTime":1648195200000
        #            }
        #            ...
        #        ]
        #    }
        #
        instrumentPairs = self.safe_value(response, 'instrumentPairs', [])
        markets = []
        for i in range(0, len(instrumentPairs)):
            market = self.parse_market(instrumentPairs[i])
            markets.append(market)
        return markets

    def parse_market(self, market):
        #
        #    {
        #        "instrumentPairs": [
        #            {
        #                "instrumentId":303,
        #                "symbol":"BTC/USDC[220325]",
        #                "quoteId":1,
        #                "baseId":3,
        #                "price_scale":2,
        #                "quantity_scale":6,
        #                "securityStatus":1,
        #                "securityDesc":"BTC Dated Future",
        #                "assetType":"DATED_FUTURE",
        #                "currency":"BTC",
        #                "contAmtCurr":"USDC",
        #                "settlCurrency":"USDC",
        #                "commCurrency":"USDC",
        #                "cfiCode":"FFCPSX",
        #                "securityExchange":"EQOS",
        #                "micCode":"EQOD",
        #                "instrumentPricePrecision":2,
        #                "minPriceIncrement":1.0,
        #                "minPriceIncrementAmount":1.0,
        #                "roundLot":100,
        #                "minTradeVol":0.000100,
        #                "maxTradeVol":0.000000,
        #                "qtyType":0,
        #                "contractMultiplier":1.0,
        #                "auctionStartTime":0,
        #                "auctionDuration":0,
        #                "auctionFrequency":0,
        #                "auctionPrice":0,
        #                "auctionVolume":0,
        #                "marketStatus":"OPEN",
        #                "underlyingSymbol":"BTC/USDC",
        #                "underlyingSecurityId":52,
        #                "underlyingSecuritySource":"M",
        #                "underlyingSecurityExchange":"EQOC",
        #                "issueDate":1643256000000,
        #                "maturityDate":"2022-03-25",
        #                "maturityTime":"2022-03-25T08:00:00Z",
        #                "contractExpireTime":1648195200000
        #            }
        #            ...
        #        ]
        #    }
        #
        assetType = self.safe_string(market, 'assetType')
        spot = (assetType == 'PAIR')
        swap = (assetType == 'PERPETUAL_SWAP')
        future = (assetType == 'DATED_FUTURE')
        contract = swap or future
        id = self.safe_string(market, 'instrumentId')
        baseId = self.safe_string(market, 'currency')
        quoteId = self.safe_string(market, 'contAmtCurr')
        settleId = self.safe_string(market, 'settlCurrency') if contract else None
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        settle = self.safe_currency_code(settleId)
        symbol = base + '/' + quote
        uppercaseId = self.safe_string(market, 'symbol')
        type = 'spot'
        linear = None
        inverse = None
        expiry = self.safe_number(market, 'contractExpireTime')
        if contract:
            symbol = symbol + ':' + settle
            linear = (quote == settle)
            inverse = not linear
            if swap:
                type = 'swap'
            elif future:
                symbol = symbol + '-' + self.yymmdd(expiry)
                type = 'future'
            else:
                symbol = uppercaseId
                type = assetType
        status = self.safe_integer(market, 'securityStatus')
        return {
            'id': id,
            'uppercaseId': uppercaseId,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'settle': settle,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': settleId,
            'type': type,
            'spot': spot,
            'margin': False,
            'swap': swap,
            'future': future,
            'option': False,
            'active': (status == 1),
            'contract': contract,
            'linear': linear,
            'inverse': inverse,
            'contractSize': self.safe_number(market, 'contractMultiplier'),
            'expiry': expiry,
            'expiryDatetime': self.iso8601(expiry),
            'strike': None,
            'optionType': None,
            'precision': {
                'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'quantity_scale'))),
                'price': self.parse_number(self.parse_precision(self.safe_string(market, 'price_scale'))),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': self.safe_number(market, 'minTradeVol'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
            'info': market,
        }

    async def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the eqonex api endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = await self.publicGetGetInstruments(params)
        #
        #     {
        #         "instruments": [
        #             [
        #                 3,     # id
        #                 "BTC",  # symbol
        #                 2,     # price_scale
        #                 6,     # amount_scale
        #                 1,     # status
        #                 0,     # withdraw_fee
        #                 "BTC",  # name
        #                 True,  # withdrawal_pct
        #             ],
        #         ]
        #     }
        #
        currencies = {}
        instruments = self.safe_value(response, 'instruments', [])
        for i in range(0, len(instruments)):
            currency = self.parse_currency(instruments[i])
            code = currency['code']
            currencies[code] = currency
        return currencies

    def parse_currency(self, currency):
        #
        #     [
        #         3,     # 0 id
        #         "BTC",  # 1 symbol
        #         2,     # 2 price_scale
        #         6,     # 3 amount_scale
        #         1,     # 4 status
        #         0,     # 5 withdraw_fee
        #         "BTC",  # 6 name
        #         True,  # 7 withdrawal_pct
        #     ],
        #
        id = self.safe_string(currency, 0)
        uppercaseId = self.safe_string(currency, 1)
        code = self.safe_currency_code(uppercaseId)
        name = self.safe_string(currency, 6)
        status = self.safe_integer(currency, 4)
        active = (status == 1)
        fee = self.safe_number(currency, 5)  # withdraw_fee
        return {
            'id': id,
            'info': currency,
            'uppercaseId': uppercaseId,
            'code': code,
            'name': name,
            'precision': self.parse_number(self.parse_precision(self.safe_string(currency, 3))),
            'fee': fee,
            'active': active,
            'deposit': None,
            'withdraw': None,
            'limits': {
                'amount': {
                    'min': None,
                    'max': None,
                },
                'withdraw': {
                    'min': None,
                    'max': None,
                },
            },
        }

    async def fetch_ohlcv(self, symbol, timeframe='1m', 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 eqonex 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 = {
            'pairId': int(market['id']),
            'timespan': self.timeframes[timeframe],
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetGetChart(self.extend(request, params))
        #
        #     {
        #         "pairId":57,
        #         "t":1,
        #         "s":"ETH/BTC",
        #         "lastPx":44099,
        #         "lastQty":100000,
        #         "o":0.043831000000000016,
        #         "h":0.04427100000000002,
        #         "l":0.032000000000000015,
        #         "c":0.04409900000000002,
        #         "v":0.21267333000000016,
        #         "q":4.850000000000001,
        #         "chart":[
        #             [1612519260000,44099,44099,44099,44099,0,441],
        #             [1612519200000,44099,44099,44099,44099,0,440],
        #             [1612519140000,44269,44271,44269,44271,0,439],
        #         ]
        #     }
        #
        chart = self.safe_value(response, 'chart', [])
        return self.parse_ohlcvs(chart, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1612519260000,  # timestamp
        #         44099,         # open
        #         44099,         # high
        #         44099,         # low
        #         44099,         # close
        #         0,             # base volume
        #         441,           # seqNumber
        #     ]
        #
        timestamp = self.safe_integer(ohlcv, 0)
        open = self.parse_number(self.convert_from_scale(self.safe_string(ohlcv, 1), market['precision']['price']))
        high = self.parse_number(self.convert_from_scale(self.safe_string(ohlcv, 2), market['precision']['price']))
        low = self.parse_number(self.convert_from_scale(self.safe_string(ohlcv, 3), market['precision']['price']))
        close = self.parse_number(self.convert_from_scale(self.safe_string(ohlcv, 4), market['precision']['price']))
        volume = self.parse_number(self.convert_from_scale(self.safe_string(ohlcv, 5), market['precision']['amount']))
        return [timestamp, open, high, low, close, volume]

    def parse_bid_ask(self, bidask, priceKey=0, amountKey=1, market=None):
        if market is None:
            raise ArgumentsRequired(self.id + ' parseBidAsk() requires a market argument')
        priceString = self.safe_string(bidask, priceKey)
        amountString = self.safe_string(bidask, amountKey)
        return [
            self.parse_number(self.convert_from_scale(priceString, market['precision']['price'])),
            self.parse_number(self.convert_from_scale(amountString, market['precision']['amount'])),
        ]

    def parse_order_book(self, orderbook, symbol, timestamp=None, bidsKey='bids', asksKey='asks', priceKey=0, amountKey=1, market=None):
        result = {
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'nonce': None,
        }
        sides = [bidsKey, asksKey]
        for i in range(0, len(sides)):
            side = sides[i]
            orders = []
            bidasks = self.safe_value(orderbook, side)
            for k in range(0, len(bidasks)):
                orders.append(self.parse_bid_ask(bidasks[k], priceKey, amountKey, market))
            result[side] = orders
        result[bidsKey] = self.sort_by(result[bidsKey], 0, True)
        result[asksKey] = self.sort_by(result[asksKey], 0)
        return result

    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 eqonex 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 = {
            'pairId': int(market['id']),
        }
        response = await self.publicGetGetOrderBook(self.extend(request, params))
        #
        #     {
        #         "bids":[
        #             [4000480,30000,1612644984667],
        #             [3999304,200000,1612644984667],
        #             [3998862,50000,1612644984667],
        #         ],
        #         "asks":[
        #             [4001962,1790000,1612644984667],
        #             [4002616,1000,1612644984667],
        #             [4003889,1000,1612644984667],
        #         ],
        #         "usdMark":40011.02,
        #         "marketStatus":0,
        #         "estFundingRate":0.0,
        #         "fundingRateTime":0,
        #         "auctionPrice":0.0,
        #         "auctionVolume":0.0
        #     }
        #
        return self.parse_order_book(response, market['symbol'], None, 'bids', 'asks', 0, 1, 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 eqonex 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 = {
            'pairId': int(market['id']),
        }
        response = await self.publicGetGetTradeHistory(self.extend(request, params))
        #
        #     {
        #         "trades":[
        #             [4022800,47000,"20210206-21:39:12.886",256323,1],
        #             [4023066,1000,"20210206-21:38:55.030",256322,1],
        #             [4022406,50000,"20210206-21:36:56.334",256321,1],
        #         ]
        #     }
        #
        trades = self.safe_value(response, 'trades', [])
        return self.parse_trades(trades, market, since, limit, params)

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #     [
        #         4022800,                 # 0 price
        #         47000,                   # 1 quantity
        #         "20210206-21:39:12.886",  # 2 timestamp
        #         256323,                  # 3 sequence number
        #         1                        # 4 taker side 1 = buy, 2 = sell
        #     ]
        #
        # private fetchMyTrades
        #
        #     {
        #         "account":3583,
        #         "commission":"-0.015805",
        #         "commCurrency":"USDC",
        #         "execId":265757,
        #         "ordType":"2",
        #         "ordStatus":"2",
        #         "execType":"F",
        #         "aggressorIndicator":true,
        #         "orderId":388953019,
        #         "price":"1842.04",
        #         "qty":"0.010000",
        #         "lastPx":"1756.22",
        #         "avgPx":"1756.22",
        #         "cumQty":"0.010000",
        #         "quoteQty":"0.010000",
        #         "side":"BUY",
        #         "symbol":"ETH/USDC",
        #         "clOrdId":"1613106766970339107",
        #         "submitterId":3583,
        #         "targetStrategy":"0",
        #         "time":1613106766971,
        #         "date":"20210212-05:12:46.971"
        #     }
        #
        id = None
        timestamp = None
        orderId = None
        type = None
        side = None
        priceString = None
        amountString = None
        fee = None
        marketId = None
        if isinstance(trade, list):
            id = self.safe_string(trade, 3)
            priceString = self.convert_from_scale(self.safe_string(trade, 0), market['precision']['price'])
            amountString = self.convert_from_scale(self.safe_string(trade, 1), market['precision']['amount'])
            timestamp = self.to_milliseconds(self.safe_string(trade, 2))
            takerSide = self.safe_integer(trade, 4)
            if takerSide == 1:
                side = 'buy'
            elif takerSide == 2:
                side = 'sell'
        else:
            id = self.safe_string(trade, 'execId')
            timestamp = self.safe_integer(trade, 'time')
            marketId = self.safe_string(trade, 'symbol')
            orderId = self.safe_string(trade, 'orderId')
            side = self.safe_string_lower(trade, 'side')
            type = self.parse_order_type(self.safe_string(trade, 'ordType'))
            priceString = self.safe_string(trade, 'lastPx')
            amountString = self.safe_string(trade, 'qty')
            feeCostString = self.safe_string(trade, 'commission')
            if feeCostString is not None:
                feeCostString = Precise.string_neg(feeCostString)
                feeCurrencyId = self.safe_string(trade, 'commCurrency')
                feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
                fee = {
                    'cost': feeCostString,
                    'currency': feeCurrencyCode,
                }
        market = self.safe_market(marketId, market)
        return self.safe_trade({
            'info': trade,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'order': orderId,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
        }, market)

    def parse_balance(self, response):
        positions = self.safe_value(response, 'positions', [])
        result = {
            'info': response,
        }
        for i in range(0, len(positions)):
            position = positions[i]
            assetType = self.safe_string(position, 'assetType')
            if assetType == 'ASSET':
                currencyId = self.safe_string(position, 'symbol')
                code = self.safe_currency_code(currencyId)
                quantityString = self.safe_string(position, 'quantity')
                availableQuantityString = self.safe_string(position, 'availableQuantity')
                scale = self.safe_integer(position, 'quantity_scale')
                account = self.account()
                account['free'] = self.convert_from_scale(availableQuantityString, scale)
                account['total'] = self.convert_from_scale(quantityString, scale)
                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 eqonex api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        await self.load_markets()
        response = await self.privatePostGetPositions(params)
        #     {
        #         "positions":[
        #             {
        #                 "instrumentId":1,
        #                 "userId":3583,
        #                 "quantity":0,
        #                 "availableQuantity":0,
        #                 "quantity_scale":6,
        #                 "symbol":"USDC",
        #                 "assetType":"ASSET",
        #                 "usdCostBasis":0.0,
        #                 "usdAvgCostBasis":0.0,
        #                 "usdValue":0.0,
        #                 "usdUnrealized":0.0,
        #                 "usdRealized":0.0,
        #                 "baseUsdMark":1.0,
        #                 "settleCoinUsdMark":0.0,
        #                 "settleCoinUnrealized":0.0,
        #                 "settleCoinRealized":0.0
        #             },
        #         ]
        #     }
        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 eqonex api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        orderSide = 1 if (side == 'buy') else 2
        quantityScale = self.get_scale(amount)
        request = {
            # 'id': 0,
            # 'account': 0,  # required for institutional users
            'instrumentId': int(market['id']),
            'symbol': market['uppercaseId'],
            # 'clOrdId': '',
            'side': orderSide,  # 1 = buy, 2 = sell
            # 'ordType': 1,  # 1 = market, 2 = limit, 3 = stop market, 4 = stop limit
            # 'price': self.price_to_precision(symbol, price),  # required for limit and stop limit orders
            # 'price_scale': self.get_scale(price),
            'quantity': self.convert_to_scale(self.number_to_string(amount), quantityScale),
            'quantity_scale': quantityScale,
            # 'stopPx': self.price_to_precision(symbol, stopPx),
            # 'stopPx_scale': self.get_scale(stopPx),
            # 'targetStrategy': 0,
            # 'isHidden': False,
            # 'timeInForce': 1,  # 1 = Good Till Cancel(GTC), 3 = Immediate or Cancel(IOC), 4 = Fill or Kill(FOK), 5 = Good Till Crossing(GTX), 6 = Good Till Date(GTD)
            # 'interval': 0,
            # 'intervalCount': 0,
            # 'intervalDelay': 0,
            # 'price2': 0,
            # 'price2_scale': self.get_scale(price2),
            # 'blockWaitAck': 0,  # 1 = wait for order acknowledgement, when set, response will include the matching engine "orderId" field
        }
        if type == 'market':
            request['ordType'] = 1
        elif type == 'limit':
            request['ordType'] = 2
            priceScale = self.get_scale(price)
            request['price'] = self.convert_to_scale(self.number_to_string(price), priceScale)
            request['priceScale'] = priceScale
        else:
            stopPrice = self.safe_number_2(params, 'stopPrice', 'stopPx')
            params = self.omit(params, ['stopPrice', 'stopPx'])
            if stopPrice is None:
                if type == 'stop':
                    if price is None:
                        raise ArgumentsRequired(self.id + ' createOrder() requires a price argument or a stopPrice parameter or a stopPx parameter for ' + type + ' orders')
                    request['ordType'] = 3
                    request['stopPx'] = self.convert_to_scale(self.number_to_string(price), self.get_scale(price))
                elif type == 'stop limit':
                    raise ArgumentsRequired(self.id + ' createOrder() requires a stopPrice parameter or a stopPx parameter for ' + type + ' orders')
            else:
                if type == 'stop':
                    request['ordType'] = 3
                    request['stopPx'] = self.convert_to_scale(self.number_to_string(stopPrice), self.get_scale(stopPrice))
                elif type == 'stop limit':
                    request['ordType'] = 4
                    priceScale = self.get_scale(price)
                    stopPriceScale = self.get_scale(stopPrice)
                    request['price_scale'] = priceScale
                    request['stopPx_scale'] = stopPriceScale
                    request['stopPx'] = self.convert_to_scale(self.number_to_string(stopPrice), stopPriceScale)
                    request['price'] = self.convert_to_scale(self.number_to_string(price), priceScale)
        response = await self.privatePostOrder(self.extend(request, params))
        #
        #     {
        #         "status":"sent",
        #         "id":385617863,
        #         "instrumentId":53,
        #         "clOrdId":"1613037510849637345",
        #         "userId":3583,
        #         "price":2000,
        #         "quantity":200,
        #         "ordType":2
        #     }
        #
        return self.parse_order(response, market)

    async def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the eqonex api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'origOrderId': int(id),
            'instrumentId': int(market['id']),
        }
        response = await self.privatePostCancelOrder(self.extend(request, params))
        #
        #     {
        #         "status":"sent",
        #         "id":0,
        #         "origOrderId":385613629,
        #         "instrumentId":53,
        #         "userId":3583,
        #         "price":0,
        #         "quantity":0,
        #         "ordType":0
        #     }
        #
        return self.parse_order(response, market)

    async def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        orderSide = 1 if (side == 'buy') else 2
        quantityScale = self.get_scale(amount)
        request = {
            # 'id': 0,
            'origOrderId': id,
            # 'account': 0,  # required for institutional users
            'instrumentId': int(market['id']),
            'symbol': market['uppercaseId'],
            # 'clOrdId': '',
            'side': orderSide,  # 1 = buy, 2 = sell
            # 'ordType': 1,  # 1 = market, 2 = limit, 3 = stop market, 4 = stop limit
            # 'price': self.price_to_precision(symbol, price),  # required for limit and stop limit orders
            # 'price_scale': self.get_scale(price),
            'quantity': self.convert_to_scale(self.number_to_string(amount), quantityScale),
            'quantity_scale': quantityScale,
            # 'stopPx': self.price_to_precision(symbol, stopPx),
            # 'stopPx_scale': self.get_scale(stopPx),
            # 'timeInForce': 1,  # 1 = Good Till Cancel(GTC), 3 = Immediate or Cancel(IOC), 4 = Fill or Kill(FOK), 5 = Good Till Crossing(GTX), 6 = Good Till Date(GTD)
        }
        if type == 'market':
            request['ordType'] = 1
        elif type == 'limit':
            request['ordType'] = 2
            request['price'] = self.convert_to_scale(self.number_to_string(price), self.get_scale(price))
        else:
            stopPrice = self.safe_number_2(params, 'stopPrice', 'stopPx')
            params = self.omit(params, ['stopPrice', 'stopPx'])
            if stopPrice is None:
                if type == 'stop':
                    if price is None:
                        raise ArgumentsRequired(self.id + ' editOrder() requires a price argument or a stopPrice parameter or a stopPx parameter for ' + type + ' orders')
                    request['ordType'] = 3
                    request['stopPx'] = self.convert_to_scale(self.number_to_string(price), self.get_scale(price))
                elif type == 'stop limit':
                    raise ArgumentsRequired(self.id + ' editOrder() requires a stopPrice parameter or a stopPx parameter for ' + type + ' orders')
            else:
                if type == 'stop':
                    request['ordType'] = 3
                    request['stopPx'] = self.convert_to_scale(self.number_to_string(stopPrice), self.get_scale(stopPrice))
                elif type == 'stop limit':
                    request['ordType'] = 4
                    priceScale = self.get_scale(price)
                    stopPriceScale = self.get_scale(stopPrice)
                    request['price_scale'] = priceScale
                    request['stopPx_scale'] = stopPriceScale
                    request['stopPx'] = self.convert_to_scale(self.number_to_string(stopPrice), stopPriceScale)
                    request['price'] = self.convert_to_scale(self.number_to_string(price), priceScale)
        response = await self.privatePostOrder(self.extend(request, params))
        #
        #     {
        #         "status":"sent",
        #         "id":385617863,
        #         "instrumentId":53,
        #         "clOrdId":"1613037510849637345",
        #         "userId":3583,
        #         "price":2000,
        #         "quantity":200,
        #         "ordType":2
        #     }
        #
        return self.parse_order(response, market)

    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 eqonex fetchOrder
        :param dict params: extra parameters specific to the eqonex api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        request = {
            'orderId': int(id),
        }
        response = await self.privatePostGetOrderStatus(self.extend(request, params))
        #
        #     {
        #         "orderId":388953019,
        #         "clOrdId":"1613106766970339107",
        #         "symbol":"ETH/USDC",
        #         "instrumentId":53,
        #         "side":"1",
        #         "userId":3583,
        #         "account":3583,
        #         "execType":"F",
        #         "ordType":"2",
        #         "ordStatus":"2",
        #         "timeInForce":"3",
        #         "timeStamp":"20210212-05:12:46.971",
        #         "execId":265757,
        #         "targetStrategy":0,
        #         "isHidden":false,
        #         "isReduceOnly":false,
        #         "isLiquidation":false,
        #         "fee":0,
        #         "fee_scale":6,
        #         "feeInstrumentId":1,
        #         "price":184204,
        #         "price_scale":2,
        #         "quantity":10000,
        #         "quantity_scale":6,
        #         "leavesQty":0,
        #         "leavesQty_scale":6,
        #         "cumQty":10000,
        #         "cumQty_scale":6,
        #         "lastPx":175622,
        #         "lastPx_scale":2,
        #         "avgPx":175622,
        #         "avgPx_scale":2,
        #         "lastQty":10000,
        #         "lastQty_scale":6
        #     }
        #
        return self.parse_order(response)

    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 eqonex api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        request = {
            'ordStatus': '2',  # '0' = New, '1' = Partially filled, '2' = Filled, '4' = Cancelled, '8' = Rejected, 'C' = Expired
        }
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def fetch_canceled_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple canceled orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: timestamp in ms of the earliest order, default is None
        :param int|None limit: max number of orders to return, default is None
        :param dict params: extra parameters specific to the eqonex api endpoint
        :returns dict: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        request = {
            'ordStatus': '4',  # '0' = New, '1' = Partially filled, '2' = Filled, '4' = Cancelled, '8' = Rejected, 'C' = Expired
        }
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    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 eqonex api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        market = None
        request = {
            # 'account': id,  # for institutional users
            # 'symbol': marketSymbol,  # cannot be used with instrumentId
            # 'instrumentId': market['numericId'],
            # 'limit': limit,
            # 'execType': execType,  # '0' = New, '4' = Canceled, '5' = Replace, '8' = Rejected, 'C' = Expired, 'F' = Fill Status, 'I' = Order Status
            # 'ordStatus': ordStatus,  # '0' = New, '1' = Partially filled, '2' = Filled, '4' = Cancelled, '8' = Rejected, 'C' = Expired
        }
        if symbol is not None:
            market = self.market(symbol)
            request['instrumentId'] = int(market['id'])
        if limit is not None:
            request['limit'] = limit
        response = await self.privatePostGetOrders(self.extend(request, params))
        #
        #     {
        #         "isInitialSnap":false,
        #         "orders":[
        #             {
        #                 "orderId":385613629,
        #                 "orderUpdateSeq":1,
        #                 "clOrdId":"1613037448945798198",
        #                 "symbol":"ETH/USDC",
        #                 "instrumentId":53,
        #                 "side":"1",
        #                 "userId":3583,
        #                 "account":3583,
        #                 "execType":"4",
        #                 "ordType":"2",
        #                 "ordStatus":"C",
        #                 "timeInForce":"3",
        #                 "timeStamp":"20210211-09:57:28.944",
        #                 "execId":0,
        #                 "targetStrategy":0,
        #                 "isHidden":false,
        #                 "isReduceOnly":false,
        #                 "isLiquidation":false,
        #                 "fee":0,
        #                 "feeTotal":0,
        #                 "fee_scale":0,
        #                 "feeInstrumentId":0,
        #                 "price":999,
        #                 "price_scale":2,
        #                 "quantity":10000000,
        #                 "quantity_scale":6,
        #                 "leavesQty":10000000,
        #                 "leavesQty_scale":6,
        #                 "cumQty":0,
        #                 "cumQty_scale":0,
        #                 "lastPx":0,
        #                 "lastPx_scale":2,
        #                 "avgPx":0,
        #                 "avgPx_scale":0,
        #                 "lastQty":0,
        #                 "lastQty_scale":6
        #             }
        #         ]
        #     }
        #
        orders = self.safe_value(response, 'orders', [])
        return self.parse_orders(orders, market, since, limit, params)

    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 eqonex api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        await self.load_markets()
        request = {
            # 'account': 123,  # for institutional users
            # 'instrumentId': market['id'],
            # 'startTime': since,
            # 'endTime': self.milliseconds(),
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrumentId'] = market['id']
        if since is not None:
            request['startTime'] = since
        response = await self.privatePostUserTrades(self.extend(request, params))
        #
        #     {
        #         "trades":[
        #             {
        #                 "account":3583,
        #                 "commission":"-0.015805",
        #                 "commCurrency":"USDC",
        #                 "execId":265757,
        #                 "ordType":"2",
        #                 "ordStatus":"2",
        #                 "execType":"F",
        #                 "aggressorIndicator":true,
        #                 "orderId":388953019,
        #                 "price":"1842.04",
        #                 "qty":"0.010000",
        #                 "lastPx":"1756.22",
        #                 "avgPx":"1756.22",
        #                 "cumQty":"0.010000",
        #                 "quoteQty":"0.010000",
        #                 "side":"BUY",
        #                 "symbol":"ETH/USDC",
        #                 "clOrdId":"1613106766970339107",
        #                 "submitterId":3583,
        #                 "targetStrategy":"0",
        #                 "time":1613106766971,
        #                 "date":"20210212-05:12:46.971"
        #             }
        #         ]
        #     }
        #
        trades = self.safe_value(response, 'trades', [])
        return self.parse_trades(trades, market, since, limit, params)

    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 eqonex 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 = {
            'instrumentId': int(currency['id']),
        }
        response = await self.privatePostGetDepositAddresses(self.extend(request, params))
        #
        #     {
        #         "addresses":[
        #             {"instrumentId":1,"userId":3583,"symbol":"USDC","address":"0xdff47af071ea3c537e57278290516cda32a78b97","status":1}
        #         ]
        #     }
        #
        addresses = self.safe_value(response, 'addresses', [])
        address = self.safe_value(addresses, 0)
        return self.parse_deposit_address(address, currency)

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     {
        #         "instrumentId":1,
        #         "userId":3583,
        #         "symbol":"USDC",
        #         "address":"0xdff47af071ea3c537e57278290516cda32a78b97",
        #         "status":1
        #     }
        #
        currencyId = self.safe_string(depositAddress, 'symbol')
        code = self.safe_currency_code(currencyId, currency)
        address = self.safe_string(depositAddress, 'address')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': None,
            'network': None,
            'info': depositAddress,
        }

    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 eqonex api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['instrumentId'] = int(currency['id'])
        response = await self.privatePostGetDepositHistory(self.extend(request, params))
        #
        #     {
        #         "deposits":[
        #             {
        #                 "id":4309,
        #                 "instrumentId":1,
        #                 "userId":3583,
        #                 "symbol":"USDC",
        #                 "address":"null",
        #                 "timestamp":"1613021112189",
        #                 "status":1,
        #                 "balance":0.0,
        #                 "balance_change":100.0,
        #                 "confirms":1,
        #                 "transactionId":"caba4500-489f-424e-abd7-b4dabc09a800"
        #             }
        #         ]
        #     }
        #
        deposits = self.safe_value(response, 'deposits', [])
        return self.parse_transactions(deposits, currency, since, limit, {'type': 'deposit'})

    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 eqonex api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['instrumentId'] = int(currency['id'])
        response = await self.privatePostGetWithdrawRequests(self.extend(request, params))
        #
        #     {
        #         "addresses":[
        #             {
        #                 "id":3841,
        #                 "instrumentId":3,
        #                 "userId":4245,
        #                 "symbol":"BTC",
        #                 "address":"XXXXXYYYYYZZZZZ",
        #                 "timestamp":"20200806-11:04:35.053",
        #                 "status":0,
        #                 "balance":1,
        #                 "balance_scale":3,
        #                 "confirms":0,
        #                 "transactionId":"null"
        #             }
        #         ]
        #     }
        #
        withdrawals = self.safe_value(response, 'addresses', [])
        return self.parse_transactions(withdrawals, currency, since, limit, {'type': 'withdrawal'})

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits, fetchWithdrawals
        #
        #     {
        #         "id":4309,
        #         "instrumentId":1,
        #         "userId":3583,
        #         "symbol":"USDC",
        #         "address":"null",
        #         "timestamp":"1613021112189",
        #         "status":1,
        #         "balance":0.0,
        #         "balance_change":100.0,
        #         "confirms":1,
        #         "transactionId":"caba4500-489f-424e-abd7-b4dabc09a800"
        #     }
        #
        # withdraw
        #
        #     {
        #         "instrumentId": 1,
        #         "userId": 23750,
        #         "symbol": "USDC",
        #         "timestamp": "20200201-05:37:16.584",
        #         "status": 1,
        #         "userUuid": "b9e33713-c28f-468f-99bd-f6deab0dd854",
        #         "currencyCode": "USDC",
        #         "address": "2MvW97yT6E2Kq8bWc1aj1DqfbgMzjRNk2LE",
        #         "quantity": 20,
        #         "requestUuid": "56782b34-8a78-4f5f-b164-4b8f7d583b7f",
        #         "transactionUuid": "1004eb0f-41e1-41e9-9d48-8eefcc6c09f2",
        #         "transactionId": "WS23436",
        #         "destinationWalletAlias": "Test",
        #         "quantity_scale": 0
        #     }
        #
        id = self.safe_string(transaction, 'id', 'transactionId')
        txid = self.safe_string(transaction, 'transactionUuid')
        timestamp = self.safe_integer(transaction, 'timestamp')
        address = self.safe_string(transaction, 'address')
        if address == 'None':
            address = None
        type = self.safe_string(transaction, 'type')
        amount = self.safe_number(transaction, 'balance_change')
        if amount is None:
            amount = self.safe_string(transaction, 'quantity')
            amountScale = self.safe_integer(transaction, 'quantity_scale')
            amount = self.parse_number(self.convert_from_scale(amount, amountScale))
        currencyId = self.safe_string(transaction, 'symbol')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'addressFrom': None,
            'address': address,
            'addressTo': None,
            'tagFrom': None,
            'tag': None,
            'tagTo': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'comment': None,
            'fee': None,
        }

    def parse_transaction_status(self, status):
        statuses = {
            '0': 'pending',
            '1': '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 eqonex 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)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        scale = self.get_scale(amount)
        quantity = self.convert_to_scale(amount, scale)
        request = {
            'instrumentId': int(currency['id']),
            'symbol': currency['uppercaseId'],
            'quantity': quantity,
            'quantity_scale': scale,
            'address': address,
        }
        response = await self.privatePostSendWithdrawRequest(self.extend(request, params))
        #
        #     {
        #         "instrumentId": 1,
        #         "userId": 23750,
        #         "symbol": "USDC",
        #         "timestamp": "20200201-05:37:16.584",
        #         "status": 1,
        #         "userUuid": "b9e33713-c28f-468f-99bd-f6deab0dd854",
        #         "currencyCode": "USDC",
        #         "address": "2MvW97yT6E2Kq8bWc1aj1DqfbgMzjRNk2LE",
        #         "quantity": 20,
        #         "requestUuid": "56782b34-8a78-4f5f-b164-4b8f7d583b7f",
        #         "transactionUuid": "1004eb0f-41e1-41e9-9d48-8eefcc6c09f2",
        #         "transactionId": "WS23436",
        #         "destinationWalletAlias": "Test",
        #         "quantity_scale": 0
        #     }
        #
        return self.parse_transaction(response, currency)

    async def fetch_trading_fees(self, params={}):
        """
        fetch the trading fees for multiple markets
        :param dict params: extra parameters specific to the eqonex api endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/en/latest/manual.html#fee-structure>` indexed by market symbols
        """
        await self.load_markets()
        response = await self.publicGetGetExchangeInfo(params)
        #
        #     {
        #         tradingLimits: [],
        #         withdrawLimits: [{All: '0.0', Type: 'percent'}],
        #         futuresFees: [
        #             {tier: '0', maker: '0.000300', taker: '0.000500'},
        #             {tier: '1', maker: '0.000200', taker: '0.000400'},
        #             {tier: '2', maker: '0.000180', taker: '0.000400'},
        #         ],
        #         spotFees: [
        #             {tier: '0', maker: '0.000900', taker: '0.001500', volume: '0'},
        #             {tier: '1', maker: '0.000600', taker: '0.001250', volume: '200000'},
        #             {tier: '2', maker: '0.000540', taker: '0.001200', volume: '2500000'},
        #         ],
        #         referrals: {earning: '0.30', discount: '0.05', duration: '180'}
        #     }
        #
        spotFees = self.safe_value(response, 'spotFees', [])
        firstSpotFee = self.safe_value(spotFees, 0, {})
        spotMakerFee = self.safe_number(firstSpotFee, 'maker')
        spotTakerFee = self.safe_number(firstSpotFee, 'taker')
        futureFees = self.safe_value(response, 'futuresFees', [])
        firstFutureFee = self.safe_value(futureFees, 0, {})
        futureMakerFee = self.safe_number(firstFutureFee, 'maker')
        futureTakerFee = self.safe_number(firstFutureFee, 'taker')
        spotTakerTiers = []
        spotMakerTiers = []
        result = {}
        for i in range(0, len(spotFees)):
            spotFee = spotFees[i]
            volume = self.safe_number(spotFee, 'volume')
            spotTakerTiers.append([volume, self.safe_number(spotFee, 'taker')])
            spotMakerTiers.append([volume, self.safe_number(spotFee, 'maker')])
        spotTiers = {
            'taker': spotTakerTiers,
            'maker': spotMakerTiers,
        }
        futureTakerTiers = []
        futureMakerTiers = []
        for i in range(0, len(futureFees)):
            futureFee = futureFees[i]
            futureTakerTiers.append([None, self.safe_number(futureFee, 'taker')])
            futureMakerTiers.append([None, self.safe_number(futureFee, 'maker')])
        futureTiers = {
            'taker': futureTakerTiers,
            'maker': futureMakerTiers,
        }
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            market = self.market(symbol)
            fee = {
                'info': response,
                'symbol': symbol,
                'percentage': True,
                'tierBased': True,
            }
            if self.safe_value(market, 'spot'):
                fee['maker'] = spotMakerFee
                fee['taker'] = spotTakerFee
                fee['tiers'] = spotTiers
            elif self.safe_value(market, 'contract'):
                fee['maker'] = futureMakerFee
                fee['taker'] = futureTakerFee
                fee['tiers'] = futureTiers
            result[symbol] = fee
        return result

    async def fetch_trading_limits(self, symbols=None, params={}):
        await self.load_markets()
        # getExchangeInfo
        response = await self.publicGetGetExchangeInfo(params)
        tradingLimits = self.safe_value(response, 'tradingLimits', [])
        # To-do parsing response when available
        return {
            'info': tradingLimits,
            'limits': {
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
        }

    async def fetch_funding_limits(self, params={}):
        # getExchangeInfo
        response = await self.publicGetGetExchangeInfo(params)
        withdrawLimits = self.safe_value(response, 'withdrawLimits', [])
        # TO-DO parse response when available
        return {
            'info': withdrawLimits,
            'withdraw': None,
        }

    def parse_order(self, order, market=None):
        #
        # createOrder, editOrder, cancelOrder
        #
        #     {
        #         "status":"sent",
        #         "id":385617863,
        #         "instrumentId":53,
        #         "clOrdId":"1613037510849637345",
        #         "userId":3583,
        #         "price":2000,
        #         "quantity":200,
        #         "ordType":2
        #     }
        #
        # fetchOrders, fetchOrder
        #
        #     {
        #         "orderId":385613629,
        #         "orderUpdateSeq":1,
        #         "clOrdId":"1613037448945798198",
        #         "symbol":"ETH/USDC",
        #         "instrumentId":53,
        #         "side":"1",
        #         "userId":3583,
        #         "account":3583,
        #         "execType":"4",
        #         "ordType":"2",
        #         "ordStatus":"C",
        #         "timeInForce":"3",
        #         "timeStamp":"20210211-09:57:28.944",
        #         "execId":0,
        #         "targetStrategy":0,
        #         "isHidden":false,
        #         "isReduceOnly":false,
        #         "isLiquidation":false,
        #         "fee":0,
        #         "feeTotal":0,
        #         "fee_scale":0,
        #         "feeInstrumentId":0,
        #         "price":999,
        #         "price_scale":2,
        #         "quantity":10000000,
        #         "quantity_scale":6,
        #         "leavesQty":10000000,
        #         "leavesQty_scale":6,
        #         "cumQty":0,
        #         "cumQty_scale":0,
        #         "lastPx":0,
        #         "lastPx_scale":2,
        #         "avgPx":0,
        #         "avgPx_scale":0,
        #         "lastQty":0,
        #         "lastQty_scale":6
        #     }
        #
        id = self.safe_string_2(order, 'orderId', 'id')
        id = self.safe_string(order, 'origOrderId', id)
        clientOrderId = self.safe_string(order, 'clOrdId')
        type = self.parse_order_type(self.safe_string(order, 'ordType'))
        side = self.parse_order_side(self.safe_string(order, 'side'))
        status = self.parse_order_status(self.safe_string(order, 'ordStatus'))
        marketId = self.safe_string(order, 'instrumentId')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.to_milliseconds(self.safe_string(order, 'timeStamp'))
        lastTradeTimestamp = None
        priceString = self.safe_string(order, 'price')
        priceScale = self.safe_integer(order, 'price_scale')
        priceString = self.convert_from_scale(priceString, priceScale)
        amountString = self.safe_string(order, 'quantity')
        amountScale = self.safe_integer(order, 'quantity_scale')
        amountString = self.convert_from_scale(amountString, amountScale)
        filledString = self.safe_string(order, 'cumQty')
        filledScale = self.safe_integer(order, 'cumQty_scale')
        filledString = self.convert_from_scale(filledString, filledScale)
        remainingString = self.safe_string(order, 'leavesQty')
        remainingScale = self.safe_integer(order, 'leavesQty_scale')
        remainingString = self.convert_from_scale(remainingString, remainingScale)
        fee = None
        currencyId = self.safe_integer(order, 'feeInstrumentId')
        feeCurrencyCode = self.safe_currency_code(currencyId)
        feeCostString = None
        feeCost = self.safe_string(order, 'feeTotal')
        feeScale = self.safe_integer(order, 'fee_scale')
        if feeCost is not None:
            feeCost = Precise.string_neg(feeCost)
            feeCostString = self.convert_from_scale(feeCost, feeScale)
        if feeCost is not None:
            fee = {
                'currency': feeCurrencyCode,
                'cost': feeCostString,
                'rate': None,
            }
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
        if timeInForce == '0':
            timeInForce = None
        stopPriceScale = self.safe_integer(order, 'stopPx_scale', 0)
        stopPrice = self.parse_number(self.convert_from_scale(self.safe_string(order, 'stopPx'), stopPriceScale))
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': priceString,
            'stopPrice': stopPrice,
            'amount': amountString,
            'cost': None,
            'average': None,
            'filled': filledString,
            'remaining': remainingString,
            'status': status,
            'fee': fee,
            'trades': None,
        }, market)

    def parse_order_status(self, status):
        statuses = {
            '0': 'open',
            '1': 'open',  # 'partially filled',
            '2': 'closed',  # 'filled',
            '3': 'open',  # 'done for day',
            '4': 'canceled',
            '5': 'canceled',  # 'replaced',
            '6': 'canceling',  # 'pending cancel',
            '7': 'canceled',  # 'stopped',
            '8': 'rejected',  # 'rejected',
            '9': 'canceled',  # 'suspended',
            'A': 'open',  # 'pending new',
            'B': 'open',  # 'calculated',
            'C': 'expired',
            'D': 'open',  # 'accepted for bidding',
            'E': 'canceling',  # 'pending replace',
            'F': 'open',  # 'partial fill or fill',
        }
        return self.safe_string(statuses, status, status)

    def parse_order_side(self, side):
        sides = {
            '1': 'buy',
            '2': 'sell',
        }
        return self.safe_string(sides, side, side)

    def parse_order_type(self, type):
        types = {
            '1': 'market',
            '2': 'limit',
            '3': 'stop',
            '4': 'stop limit',
        }
        return self.safe_string(types, type, type)

    def parse_time_in_force(self, timeInForce):
        timeInForces = {
            '1': 'GTC',  # Good Till Canceled
            '3': 'IOC',  # Immediate or Cancel
            '4': 'FOK',  # Fill or Kill
            '5': 'GTX',  # Good Till Crossing(GTX)
            '6': 'GTD',  # Good Till Date
        }
        return self.safe_string(timeInForces, timeInForce, timeInForce)

    def to_milliseconds(self, dateString):
        if dateString is None:
            return dateString
        # '20200328-10:31:01.575' -> '2020-03-28 12:42:48.000'
        splits = dateString.split('-')
        partOne = self.safe_string(splits, 0)
        partTwo = self.safe_string(splits, 1)
        if partOne is None or partTwo is None:
            return None
        if len(partOne) != 8:
            return None
        date = partOne[0:4] + '-' + partOne[4:6] + '-' + partOne[6:8]
        return self.parse8601(date + ' ' + partTwo)

    def convert_from_scale(self, number, scaleInTicksize):
        scale = self.get_scale(scaleInTicksize)
        if (number is None) or (scale is None):
            return None
        precise = Precise(number)
        precise.decimals = precise.decimals + scale
        precise.reduce()
        return str(precise)

    def get_scale(self, num):
        s = self.number_to_string(num)
        return self.precision_from_string(s)

    def convert_to_scale(self, number, scale):
        if (number is None) or (scale is None):
            return None
        precise = Precise(number)
        precise.decimals = precise.decimals - scale
        precise.reduce()
        preciseString = str(precise)
        return int(preciseString)

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

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        error = self.safe_string(response, 'error')
        if error is not None:
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions, error, feedback)
            self.throw_broadly_matched_exception(self.exceptions, body, feedback)
            raise ExchangeError(self.id + ' ' + body)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        elif api == 'private':
            # special case for getUserHistory
            format = self.safe_value(params, 'format')
            type = self.safe_value(params, 'type')
            extension = {}
            if format is not None:
                extension['format'] = format
            if type is not None:
                extension['type'] = type
            if extension:
                url += '?' + self.urlencode(extension)
            params = self.omit(params, ['format', 'type'])
            self.check_required_credentials()
            nonce = self.nonce()
            query = self.extend(query, {
                'userId': self.uid,
                'nonce': nonce,
            })
            params['nonce'] = self.nonce()
            body = self.json(query)
            signature = self.hmac(self.encode(body), self.encode(self.secret), hashlib.sha384)
            headers = {
                'Content-Type': 'application/json',
                'requestToken': self.apiKey,
                'signature': signature,
            }
        url = self.urls['api'][api] + '/' + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}
