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

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

from ccxt.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadSymbol


class equos(Exchange):

    def describe(self):
        return self.deep_extend(super(equos, self).describe(), {
            'id': 'equos',
            'name': 'EQUOS',
            'countries': ['US', 'SG'],  # United States, Singapore
            'rateLimit': 10,
            'has': {
                'CORS': False,
                'cancelOrder': True,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchCanceledOrders': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchTicker': False,
                'fetchTrades': True,
                'fetchTradingFees': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': 1,
                '5m': 2,
                '15m': 3,
                '1h': 4,
                '6h': 5,
                '1d': 6,
                '7d': 7,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/107758499-05edd180-6d38-11eb-9e09-0b69602a7a15.jpg',
                'test': {
                    'public': 'https://testnet.equos.io/api',
                    'private': 'https://testnet.equos.io/api',
                },
                'api': {
                    'public': 'https://equos.io/api',
                    'private': 'https://equos.io/api',
                },
                'www': 'https://equos.io',
                'doc': [
                    'https://developer.equos.io',
                ],
                'referral': 'https://equos.io?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,
            },
            'exceptions': {
                'broad': {
                    'symbol not found': BadSymbol,
                },
            },
        })

    def fetch_markets(self, params={}):
        request = {
            'verbose': True,
        }
        response = self.publicGetGetInstrumentPairs(self.extend(request, params))
        #
        #     {
        #         "instrumentPairs":[
        #             {
        #                 "instrumentId":52,
        #                 "symbol":"BTC/USDC",
        #                 "quoteId":1,
        #                 "baseId":3,
        #                 "price_scale":2,
        #                 "quantity_scale":6,
        #                 "securityStatus":1,
        #                 "securityDesc":"BTC/USDC",  # "BTC/USDC[F]"
        #                 "assetType":"PAIR",  # "PERPETUAL_SWAP"
        #                 "currency":"BTC",
        #                 "contAmtCurr":"USDC",
        #                 "settlCurrency":"USDC",
        #                 "commCurrency":"USDC",
        #                 "cfiCode":"XXXXXX",
        #                 "securityExchange":"XXXX",
        #                 "instrumentPricePrecision":2,
        #                 "minPriceIncrement":1.0,
        #                 "minPriceIncrementAmount":1.0,
        #                 "roundLot":1,
        #                 "minTradeVol":0.001000,
        #                 "maxTradeVol":0.000000
        #                 # contracts onlye
        #                 "qtyType":0,
        #                 "contractMultiplier":1.0,
        #                 "issueDate":1598608087000
        #             },
        #         ]
        #     }
        #
        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):
        #
        #     {
        #         "instrumentId":52,
        #         "symbol":"BTC/USDC",  # "BTC/USDC[F]"
        #         "quoteId":1,
        #         "baseId":3,
        #         "price_scale":2,
        #         "quantity_scale":6,
        #         "securityStatus":1,
        #         "securityDesc":"BTC/USDC",  # "BTC/USDC[F]"
        #         "assetType":"PAIR",  # "PERPETUAL_SWAP"
        #         "currency":"BTC",
        #         "contAmtCurr":"USDC",
        #         "settlCurrency":"USDC",
        #         "commCurrency":"USDC",
        #         "cfiCode":"XXXXXX",
        #         "securityExchange":"XXXX",
        #         "instrumentPricePrecision":2,
        #         "minPriceIncrement":1.0,
        #         "minPriceIncrementAmount":1.0,
        #         "roundLot":1,
        #         "minTradeVol":0.001000,
        #         "maxTradeVol":0.000000
        #         # contracts onlye
        #         "qtyType":0,
        #         "contractMultiplier":1.0,
        #         "issueDate":1598608087000
        #     }
        #
        id = self.safe_string(market, 'instrumentId')
        uppercaseId = self.safe_string(market, 'symbol')
        assetType = self.safe_string(market, 'assetType')
        spot = (assetType == 'PAIR')
        swap = (assetType == 'PERPETUAL_SWAP')
        type = 'swap' if swap else 'spot'
        baseId = self.safe_string(market, 'currency')
        quoteId = self.safe_string(market, 'contAmtCurr')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = uppercaseId if swap else (base + '/' + quote)
        status = self.safe_integer(market, 'securityStatus')
        active = (status == 1)
        precision = {
            'amount': self.safe_integer(market, 'quantity_scale'),
            'price': self.safe_integer(market, 'price_scale'),
        }
        return {
            'id': id,
            'uppercaseId': uppercaseId,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
            'type': type,
            'spot': spot,
            'swap': swap,
            'active': active,
            'precision': precision,
            'limits': {
                'amount': {
                    'min': self.safe_number(market, 'minTradeVol'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
            'info': market,
        }

    def fetch_currencies(self, params={}):
        response = 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)
        priceScale = self.safe_integer(currency, 2)
        amountScale = self.safe_integer(currency, 3)
        precision = max(priceScale, amountScale)
        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': precision,
            'fee': fee,
            'active': active,
            'limits': {
                'amount': {
                    'min': None,
                    'max': None,
                },
                'withdraw': {
                    'min': None,
                    'max': None,
                },
            },
        }

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        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 = 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.convert_from_scale(ohlcv[1], market['precision']['price'])
        high = self.convert_from_scale(ohlcv[2], market['precision']['price'])
        low = self.convert_from_scale(ohlcv[3], market['precision']['price'])
        close = self.convert_from_scale(ohlcv[4], market['precision']['price'])
        volume = self.convert_from_scale(ohlcv[5], market['precision']['amount'])
        # volume = ohlcv[5]
        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')
        price = self.safe_number(bidask, priceKey)
        amount = self.safe_number(bidask, amountKey)
        return [
            self.convert_from_scale(price, market['precision']['price']),
            self.convert_from_scale(amount, 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

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pairId': int(market['id']),
        }
        response = 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, symbol, None, 'bids', 'asks', 0, 1, market)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'pairId': int(market['id']),
        }
        response = 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
        price = None
        amount = None
        cost = None
        fee = None
        symbol = None
        if isinstance(trade, list):
            id = self.safe_string(trade, 3)
            price = self.convert_from_scale(self.safe_integer(trade, 0), market['precision']['price'])
            amount = self.convert_from_scale(self.safe_integer(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')
            symbol = self.safe_symbol(marketId, market)
            orderId = self.safe_string(trade, 'orderId')
            side = self.safe_string_lower(trade, 'side')
            type = self.parse_order_type(self.safe_string(trade, 'ordType'))
            price = self.safe_number(trade, 'lastPx')
            amount = self.safe_number(trade, 'quoteQty')
            feeCost = self.safe_number(trade, 'commission')
            if feeCost is not None:
                feeCost = -feeCost
                feeCurrencyId = self.safe_string(trade, 'commCurrency')
                feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
                fee = {
                    'cost': feeCost,
                    'currency': feeCurrencyCode,
                }
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        if cost is None:
            if (amount is not None) and (price is not None):
                cost = amount * price
        return {
            'info': trade,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_balance(self, params={}):
        self.load_markets()
        response = 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
        #             },
        #         ]
        #     }
        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)
                quantity = self.safe_number(position, 'quantity')
                availableQuantity = self.safe_number(position, 'availableQuantity')
                scale = self.safe_integer(position, 'quantity_scale')
                account = self.account()
                account['free'] = self.convert_from_scale(availableQuantity, scale)
                account['total'] = self.convert_from_scale(quantity, scale)
                result[code] = account
        return self.parse_balance(result)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        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(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
            request['price'] = self.convert_to_scale(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 + ' 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(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(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(stopPrice, stopPriceScale)
                    request['price'] = self.convert_to_scale(price, priceScale)
        response = 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)

    def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'origOrderId': int(id),
            'instrumentId': int(market['id']),
        }
        response = 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)

    def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        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(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(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(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(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(stopPrice, stopPriceScale)
                    request['price'] = self.convert_to_scale(price, priceScale)
        response = 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)

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

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'ordStatus': '2',  # '0' = New, '1' = Partially filled, '2' = Filled, '4' = Cancelled, '8' = Rejected, 'C' = Expired
        }
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def fetch_canceled_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'ordStatus': '4',  # '0' = New, '1' = Partially filled, '2' = Filled, '4' = Cancelled, '8' = Rejected, 'C' = Expired
        }
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        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 = 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)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        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 = 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)

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

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['instrumentId'] = int(currency['id'])
        response = 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'})

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['instrumentId'] = int(currency['id'])
        response = 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_integer(transaction, 'quantity')
            amountScale = self.safe_integer(transaction, 'quantity_scale')
            amount = 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),
            '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)

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        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 = 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)

    def fetch_trading_fees(self, params={}):
        # getExchangeInfo
        response = self.publicGetGetExchangeInfo(params)
        tradingFees = self.safe_value(response, 'spotFees', [])
        taker = {}
        maker = {}
        for i in range(0, len(tradingFees)):
            tradingFee = tradingFees[i]
            if self.safe_string(tradingFee, 'tier') is not None:
                taker[tradingFee['tier']] = self.safe_number(tradingFee, 'taker')
                maker[tradingFee['tier']] = self.safe_number(tradingFee, 'maker')
        return {
            'info': tradingFees,
            'tierBased': True,
            'maker': maker,
            'taker': taker,
        }

    def fetch_trading_limits(self, symbols=None, params={}):
        self.load_markets()
        # getExchangeInfo
        response = 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,
                },
            },
        }

    def fetch_funding_limits(self, params={}):
        # getExchangeInfo
        response = 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
        price = self.safe_integer(order, 'price')
        if price == 0:
            price = None
        priceScale = self.safe_integer(order, 'price_scale')
        price = self.convert_from_scale(price, priceScale)
        amount = self.safe_integer(order, 'quantity')
        if amount == 0:
            amount = None
        amountScale = self.safe_integer(order, 'quantity_scale')
        amount = self.convert_from_scale(amount, amountScale)
        filled = self.safe_integer(order, 'cumQty')
        filledScale = self.safe_integer(order, 'cumQty_scale')
        filled = self.convert_from_scale(filled, filledScale)
        remaining = self.safe_integer(order, 'leavesQty')
        remainingScale = self.safe_integer(order, 'leavesQty_scale')
        remaining = self.convert_from_scale(remaining, remainingScale)
        fee = None
        currencyId = self.safe_integer(order, 'feeInstrumentId')
        feeCurrencyCode = self.safe_currency_code(currencyId)
        feeCost = self.safe_integer(order, 'feeTotal')
        feeScale = self.safe_integer(order, 'fee_scale')
        if feeCost is not None:
            feeCost = -feeCost
            feeCost = self.convert_from_scale(feeCost, feeScale)
        if feeCost is not None:
            fee = {
                'currency': feeCurrencyCode,
                'cost': feeCost,
                '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.convert_from_scale(self.safe_integer(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': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': None,
        })

    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, scale):
        if (number is None) or (scale is None):
            return None
        return self.from_wei(number, scale)

    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
        return int(self.to_wei(number, scale))

    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}
