# -*- 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 AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.decimal_to_precision import TICK_SIZE


class ascendex(Exchange):

    def describe(self):
        return self.deep_extend(super(ascendex, self).describe(), {
            'id': 'ascendex',
            'name': 'AscendEX',
            'countries': ['SG'],  # Singapore
            'rateLimit': 500,
            'certified': True,
            # new metainfo interface
            'has': {
                'spot': True,
                'margin': None,
                'swap': True,
                'future': False,
                'option': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'CORS': None,
                'createOrder': True,
                'fetchAccounts': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingRates': True,
                'fetchMarkets': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchPositions': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTransactions': True,
                'fetchWithdrawals': True,
                'setLeverage': True,
                'setMarginMode': True,
            },
            'timeframes': {
                '1m': '1',
                '5m': '5',
                '15m': '15',
                '30m': '30',
                '1h': '60',
                '2h': '120',
                '4h': '240',
                '6h': '360',
                '12h': '720',
                '1d': '1d',
                '1w': '1w',
                '1M': '1m',
            },
            'version': 'v2',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/112027508-47984600-8b48-11eb-9e17-d26459cc36c6.jpg',
                'api': 'https://ascendex.com',
                'test': 'https://bitmax-test.io',
                'www': 'https://ascendex.com',
                'doc': [
                    'https://bitmax-exchange.github.io/bitmax-pro-api/#bitmax-pro-api-documentation',
                ],
                'fees': 'https://ascendex.com/en/feerate/transactionfee-traderate',
                'referral': {
                    'url': 'https://ascendex.com/en-us/register?inviteCode=EL6BXBQM',
                    'discount': 0.25,
                },
            },
            'api': {
                'v1': {
                    'public': {
                        'get': [
                            'assets',
                            'products',
                            'ticker',
                            'barhist/info',
                            'barhist',
                            'depth',
                            'trades',
                            'cash/assets',  # not documented
                            'cash/products',  # not documented
                            'margin/assets',  # not documented
                            'margin/products',  # not documented
                            'futures/collateral',
                            'futures/contracts',
                            'futures/ref-px',
                            'futures/market-data',
                            'futures/funding-rates',
                        ],
                    },
                    'private': {
                        'get': [
                            'info',
                            'wallet/transactions',
                            'wallet/deposit/address',  # not documented
                            'data/balance/snapshot',
                            'data/balance/history',
                        ],
                        'accountCategory': {
                            'get': [
                                'balance',
                                'order/open',
                                'order/status',
                                'order/hist/current',
                                'risk',
                            ],
                            'post': [
                                'order',
                                'order/batch',
                            ],
                            'delete': [
                                'order',
                                'order/all',
                                'order/batch',
                            ],
                        },
                        'accountGroup': {
                            'get': [
                                'cash/balance',
                                'margin/balance',
                                'margin/risk',
                                'transfer',
                                'futures/collateral-balance',
                                'futures/position',
                                'futures/risk',
                                'futures/funding-payments',
                                'order/hist',
                            ],
                            'post': [
                                'futures/transfer/deposit',
                                'futures/transfer/withdraw',
                            ],
                        },
                    },
                },
                'v2': {
                    'public': {
                        'get': [
                            'assets',
                            'futures/contract',
                            'futures/collateral',
                            'futures/pricing-data',
                        ],
                    },
                    'private': {
                        'get': [
                            'account/info',
                        ],
                        'accountGroup': {
                            'get': [
                                'order/hist',
                                'futures/position',
                                'futures/free-margin',
                                'futures/order/hist/current',
                                'futures/order/open',
                                'futures/order/status',
                            ],
                            'post': [
                                'futures/isolated-position-margin',
                                'futures/margin-type',
                                'futures/leverage',
                                'futures/transfer/deposit',
                                'futures/transfer/withdraw',
                                'futures/order',
                                'futures/order/batch',
                                'futures/order/open',
                                'subuser/subuser-transfer',
                                'subuser/subuser-transfer-hist',
                            ],
                            'delete': [
                                'futures/order',
                                'futures/order/batch',
                                'futures/order/all',
                            ],
                        },
                    },
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': True,
                    'percentage': True,
                    'taker': self.parse_number('0.002'),
                    'maker': self.parse_number('0.002'),
                },
            },
            'precisionMode': TICK_SIZE,
            'options': {
                'account-category': 'cash',  # 'cash', 'margin', 'futures'  # obsolete
                'account-group': None,
                'fetchClosedOrders': {
                    'method': 'v1PrivateAccountGroupGetOrderHist',  # 'v1PrivateAccountGroupGetAccountCategoryOrderHistCurrent'
                },
                'defaultType': 'spot',  # 'spot', 'margin', 'swap'
                'accountCategories': {
                    'spot': 'cash',
                    'swap': 'futures',
                    'margin': 'margin',
                },
            },
            'exceptions': {
                'exact': {
                    # not documented
                    '1900': BadRequest,  # {"code":1900,"message":"Invalid Http Request Input"}
                    '2100': AuthenticationError,  # {"code":2100,"message":"ApiKeyFailure"}
                    '5002': BadSymbol,  # {"code":5002,"message":"Invalid Symbol"}
                    '6001': BadSymbol,  # {"code":6001,"message":"Trading is disabled on symbol."}
                    '6010': InsufficientFunds,  # {'code': 6010, 'message': 'Not enough balance.'}
                    '60060': InvalidOrder,  # {'code': 60060, 'message': 'The order is already filled or canceled.'}
                    '600503': InvalidOrder,  # {"code":600503,"message":"Notional is too small."}
                    # documented
                    '100001': BadRequest,  # INVALID_HTTP_INPUT Http request is invalid
                    '100002': BadRequest,  # DATA_NOT_AVAILABLE Some required data is missing
                    '100003': BadRequest,  # KEY_CONFLICT The same key exists already
                    '100004': BadRequest,  # INVALID_REQUEST_DATA The HTTP request contains invalid field or argument
                    '100005': BadRequest,  # INVALID_WS_REQUEST_DATA Websocket request contains invalid field or argument
                    '100006': BadRequest,  # INVALID_ARGUMENT The arugment is invalid
                    '100007': BadRequest,  # ENCRYPTION_ERROR Something wrong with data encryption
                    '100008': BadSymbol,  # SYMBOL_ERROR Symbol does not exist or not valid for the request
                    '100009': AuthenticationError,  # AUTHORIZATION_NEEDED Authorization is require for the API access or request
                    '100010': BadRequest,  # INVALID_OPERATION The action is invalid or not allowed for the account
                    '100011': BadRequest,  # INVALID_TIMESTAMP Not a valid timestamp
                    '100012': BadRequest,  # INVALID_STR_FORMAT String format does not
                    '100013': BadRequest,  # INVALID_NUM_FORMAT Invalid number input
                    '100101': ExchangeError,  # UNKNOWN_ERROR Some unknown error
                    '150001': BadRequest,  # INVALID_JSON_FORMAT Require a valid json object
                    '200001': AuthenticationError,  # AUTHENTICATION_FAILED Authorization failed
                    '200002': ExchangeError,  # TOO_MANY_ATTEMPTS Tried and failed too many times
                    '200003': ExchangeError,  # ACCOUNT_NOT_FOUND Account not exist
                    '200004': ExchangeError,  # ACCOUNT_NOT_SETUP Account not setup properly
                    '200005': ExchangeError,  # ACCOUNT_ALREADY_EXIST Account already exist
                    '200006': ExchangeError,  # ACCOUNT_ERROR Some error related with error
                    '200007': ExchangeError,  # CODE_NOT_FOUND
                    '200008': ExchangeError,  # CODE_EXPIRED Code expired
                    '200009': ExchangeError,  # CODE_MISMATCH Code does not match
                    '200010': AuthenticationError,  # PASSWORD_ERROR Wrong assword
                    '200011': ExchangeError,  # CODE_GEN_FAILED Do not generate required code promptly
                    '200012': ExchangeError,  # FAKE_COKE_VERIFY
                    '200013': ExchangeError,  # SECURITY_ALERT Provide security alert message
                    '200014': PermissionDenied,  # RESTRICTED_ACCOUNT Account is restricted for certain activity, such as trading, or withdraw.
                    '200015': PermissionDenied,  # PERMISSION_DENIED No enough permission for the operation
                    '300001': InvalidOrder,  # INVALID_PRICE Order price is invalid
                    '300002': InvalidOrder,  # INVALID_QTY Order size is invalid
                    '300003': InvalidOrder,  # INVALID_SIDE Order side is invalid
                    '300004': InvalidOrder,  # INVALID_NOTIONAL Notional is too small or too large
                    '300005': InvalidOrder,  # INVALID_TYPE Order typs is invalid
                    '300006': InvalidOrder,  # INVALID_ORDER_ID Order id is invalid
                    '300007': InvalidOrder,  # INVALID_TIME_IN_FORCE Time In Force in order request is invalid
                    '300008': InvalidOrder,  # INVALID_ORDER_PARAMETER Some order parameter is invalid
                    '300009': InvalidOrder,  # TRADING_VIOLATION Trading violation on account or asset
                    '300011': InsufficientFunds,  # INVALID_BALANCE No enough account or asset balance for the trading
                    '300012': BadSymbol,  # INVALID_PRODUCT Not a valid product supported by exchange
                    '300013': InvalidOrder,  # INVALID_BATCH_ORDER Some or all orders are invalid in batch order request
                    '300014': InvalidOrder,  # {"code":300014,"message":"Order price doesn't conform to the required tick size: 0.1","reason":"TICK_SIZE_VIOLATION"}
                    '300020': InvalidOrder,  # TRADING_RESTRICTED There is some trading restriction on account or asset
                    '300021': InvalidOrder,  # TRADING_DISABLED Trading is disabled on account or asset
                    '300031': InvalidOrder,  # NO_MARKET_PRICE No market price for market type order trading
                    '310001': InsufficientFunds,  # INVALID_MARGIN_BALANCE No enough margin balance
                    '310002': InvalidOrder,  # INVALID_MARGIN_ACCOUNT Not a valid account for margin trading
                    '310003': InvalidOrder,  # MARGIN_TOO_RISKY Leverage is too high
                    '310004': BadSymbol,  # INVALID_MARGIN_ASSET This asset does not support margin trading
                    '310005': InvalidOrder,  # INVALID_REFERENCE_PRICE There is no valid reference price
                    '510001': ExchangeError,  # SERVER_ERROR Something wrong with server.
                    '900001': ExchangeError,  # HUMAN_CHALLENGE Human change do not pass
                },
                'broad': {},
            },
            'commonCurrencies': {
                'BOND': 'BONDED',
                'BTCBEAR': 'BEAR',
                'BTCBULL': 'BULL',
                'BYN': 'BeyondFi',
            },
        })

    def get_account(self, params={}):
        # get current or provided bitmax sub-account
        account = self.safe_value(params, 'account', self.options['account'])
        return account.lower().capitalize()

    def fetch_currencies(self, params={}):
        assets = self.v1PublicGetAssets(params)
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "assetCode" : "LTCBULL",
        #                 "assetName" : "3X Long LTC Token",
        #                 "precisionScale" : 9,
        #                 "nativeScale" : 4,
        #                 "withdrawalFee" : "0.2",
        #                 "minWithdrawalAmt" : "1.0",
        #                 "status" : "Normal"
        #             },
        #         ]
        #     }
        #
        margin = self.v1PublicGetMarginAssets(params)
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "assetCode":"BTT",
        #                 "borrowAssetCode":"BTT-B",
        #                 "interestAssetCode":"BTT-I",
        #                 "nativeScale":0,
        #                 "numConfirmations":1,
        #                 "withdrawFee":"100.0",
        #                 "minWithdrawalAmt":"1000.0",
        #                 "statusCode":"Normal",
        #                 "statusMessage":"",
        #                 "interestRate":"0.001"
        #             }
        #         ]
        #     }
        #
        cash = self.v1PublicGetCashAssets(params)
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "assetCode":"LTCBULL",
        #                 "nativeScale":4,
        #                 "numConfirmations":20,
        #                 "withdrawFee":"0.2",
        #                 "minWithdrawalAmt":"1.0",
        #                 "statusCode":"Normal",
        #                 "statusMessage":""
        #             }
        #         ]
        #     }
        #
        assetsData = self.safe_value(assets, 'data', [])
        marginData = self.safe_value(margin, 'data', [])
        cashData = self.safe_value(cash, 'data', [])
        assetsById = self.index_by(assetsData, 'assetCode')
        marginById = self.index_by(marginData, 'assetCode')
        cashById = self.index_by(cashData, 'assetCode')
        dataById = self.deep_extend(assetsById, marginById, cashById)
        ids = list(dataById.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            currency = dataById[id]
            code = self.safe_currency_code(id)
            precision = self.safe_string_2(currency, 'precisionScale', 'nativeScale')
            minAmount = self.parse_precision(precision)
            # why would the exchange API have different names for the same field
            fee = self.safe_number_2(currency, 'withdrawFee', 'withdrawalFee')
            status = self.safe_string_2(currency, 'status', 'statusCode')
            active = (status == 'Normal')
            margin = ('borrowAssetCode' in currency)
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'type': None,
                'margin': margin,
                'name': self.safe_string(currency, 'assetName'),
                'active': active,
                'deposit': None,
                'withdraw': None,
                'fee': fee,
                'precision': int(precision),
                'limits': {
                    'amount': {
                        'min': self.parse_number(minAmount),
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_number(currency, 'minWithdrawalAmt'),
                        'max': None,
                    },
                },
            }
        return result

    def fetch_markets(self, params={}):
        products = self.v1PublicGetProducts(params)
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "symbol":"LBA/BTC",
        #                 "baseAsset":"LBA",
        #                 "quoteAsset":"BTC",
        #                 "status":"Normal",
        #                 "minNotional":"0.000625",
        #                 "maxNotional":"6.25",
        #                 "marginTradable":false,
        #                 "commissionType":"Quote",
        #                 "commissionReserveRate":"0.001",
        #                 "tickSize":"0.000000001",
        #                 "lotSize":"1"
        #             },
        #         ]
        #     }
        #
        cash = self.v1PublicGetCashProducts(params)
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "symbol":"QTUM/BTC",
        #                 "domain":"BTC",
        #                 "tradingStartTime":1569506400000,
        #                 "collapseDecimals":"0.0001,0.000001,0.00000001",
        #                 "minQty":"0.000000001",
        #                 "maxQty":"1000000000",
        #                 "minNotional":"0.000625",
        #                 "maxNotional":"12.5",
        #                 "statusCode":"Normal",
        #                 "statusMessage":"",
        #                 "tickSize":"0.00000001",
        #                 "useTick":false,
        #                 "lotSize":"0.1",
        #                 "useLot":false,
        #                 "commissionType":"Quote",
        #                 "commissionReserveRate":"0.001",
        #                 "qtyScale":1,
        #                 "priceScale":8,
        #                 "notionalScale":4
        #             }
        #         ]
        #     }
        #
        perpetuals = self.v2PublicGetFuturesContract(params)
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "symbol":"BTC-PERP",
        #                 "status":"Normal",
        #                 "displayName":"BTCUSDT",
        #                 "settlementAsset":"USDT",
        #                 "underlying":"BTC/USDT",
        #                 "tradingStartTime":1579701600000,
        #                 "priceFilter":{"minPrice":"1","maxPrice":"1000000","tickSize":"1"},
        #                 "lotSizeFilter":{"minQty":"0.0001","maxQty":"1000000000","lotSize":"0.0001"},
        #                 "commissionType":"Quote",
        #                 "commissionReserveRate":"0.001",
        #                 "marketOrderPriceMarkup":"0.03",
        #                 "marginRequirements":[
        #                     {"positionNotionalLowerBound":"0","positionNotionalUpperBound":"50000","initialMarginRate":"0.01","maintenanceMarginRate":"0.006"},
        #                     {"positionNotionalLowerBound":"50000","positionNotionalUpperBound":"200000","initialMarginRate":"0.02","maintenanceMarginRate":"0.012"},
        #                     {"positionNotionalLowerBound":"200000","positionNotionalUpperBound":"2000000","initialMarginRate":"0.04","maintenanceMarginRate":"0.024"},
        #                     {"positionNotionalLowerBound":"2000000","positionNotionalUpperBound":"20000000","initialMarginRate":"0.1","maintenanceMarginRate":"0.06"},
        #                     {"positionNotionalLowerBound":"20000000","positionNotionalUpperBound":"40000000","initialMarginRate":"0.2","maintenanceMarginRate":"0.12"},
        #                     {"positionNotionalLowerBound":"40000000","positionNotionalUpperBound":"1000000000","initialMarginRate":"0.333333","maintenanceMarginRate":"0.2"}
        #                 ]
        #             }
        #         ]
        #     }
        #
        productsData = self.safe_value(products, 'data', [])
        productsById = self.index_by(productsData, 'symbol')
        cashData = self.safe_value(cash, 'data', [])
        perpetualsData = self.safe_value(perpetuals, 'data', [])
        cashAndPerpetualsData = self.array_concat(cashData, perpetualsData)
        cashAndPerpetualsById = self.index_by(cashAndPerpetualsData, 'symbol')
        dataById = self.deep_extend(productsById, cashAndPerpetualsById)
        ids = list(dataById.keys())
        result = []
        for i in range(0, len(ids)):
            id = ids[i]
            market = dataById[id]
            baseId = self.safe_string(market, 'baseAsset')
            quoteId = self.safe_string(market, 'quoteAsset')
            settleId = self.safe_value(market, 'settlementAsset')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            settle = self.safe_currency_code(settleId)
            precision = {
                'amount': self.safe_number(market, 'lotSize'),
                'price': self.safe_number(market, 'tickSize'),
            }
            status = self.safe_string(market, 'status')
            active = (status == 'Normal')
            spot = settle is None
            swap = not spot
            type = 'swap' if swap else 'spot'
            margin = self.safe_value(market, 'marginTradable', False)
            linear = True if swap else None
            contractSize = self.parse_number('1') if swap else None
            minQty = self.safe_number(market, 'minQty')
            maxQty = self.safe_number(market, 'maxQty')
            minPrice = self.safe_number(market, 'tickSize')
            maxPrice = None
            symbol = base + '/' + quote
            if swap:
                lotSizeFilter = self.safe_value(market, 'lotSizeFilter')
                minQty = self.safe_number(lotSizeFilter, 'minQty')
                maxQty = self.safe_number(lotSizeFilter, 'maxQty')
                priceFilter = self.safe_value(market, 'priceFilter')
                minPrice = self.safe_number(priceFilter, 'minPrice')
                maxPrice = self.safe_number(priceFilter, 'maxPrice')
                underlying = self.safe_string(market, 'underlying')
                parts = underlying.split('/')
                baseId = self.safe_string(parts, 0)
                quoteId = self.safe_string(parts, 1)
                base = self.safe_currency_code(baseId)
                quote = self.safe_currency_code(quoteId)
                symbol = base + '/' + quote + ':' + settle
            fee = self.safe_number(market, 'commissionReserveRate')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': settle,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': settleId,
                'type': type,
                'spot': spot,
                'margin': margin,
                'swap': swap,
                'future': False,
                'option': False,
                'active': active,
                'contract': swap,
                'linear': linear,
                'inverse': not linear if swap else None,
                'taker': fee,
                'maker': fee,
                'contractSize': contractSize,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': precision,
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': minQty,
                        'max': maxQty,
                    },
                    'price': {
                        'min': minPrice,
                        'max': maxPrice,
                    },
                    'cost': {
                        'min': self.safe_number(market, 'minNotional'),
                        'max': self.safe_number(market, 'maxNotional'),
                    },
                },
                'info': market,
            })
        return result

    def fetch_accounts(self, params={}):
        accountGroup = self.safe_string(self.options, 'account-group')
        response = None
        if accountGroup is None:
            response = self.v1PrivateGetInfo(params)
            #
            #     {
            #         "code":0,
            #         "data":{
            #             "email":"igor.kroitor@gmail.com",
            #             "accountGroup":8,
            #             "viewPermission":true,
            #             "tradePermission":true,
            #             "transferPermission":true,
            #             "cashAccount":["cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda"],
            #             "marginAccount":["martXoh1v1N3EMQC5FDtSj5VHso8aI2Z"],
            #             "futuresAccount":["futc9r7UmFJAyBY2rE3beA2JFxav2XFF"],
            #             "userUID":"U6491137460"
            #         }
            #     }
            #
            data = self.safe_value(response, 'data', {})
            accountGroup = self.safe_string(data, 'accountGroup')
            self.options['account-group'] = accountGroup
        return [
            {
                'id': accountGroup,
                'type': None,
                'currency': None,
                'info': response,
            },
        ]

    def parse_balance(self, response):
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        balances = self.safe_value(response, 'data', [])
        for i in range(0, len(balances)):
            balance = balances[i]
            code = self.safe_currency_code(self.safe_string(balance, 'asset'))
            account = self.account()
            account['free'] = self.safe_string(balance, 'availableBalance')
            account['total'] = self.safe_string(balance, 'totalBalance')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}):
        self.load_markets()
        self.load_accounts()
        defaultAccountCategory = self.safe_string(self.options, 'account-category', 'cash')
        options = self.safe_value(self.options, 'fetchBalance', {})
        accountCategory = self.safe_string(options, 'account-category', defaultAccountCategory)
        accountCategory = self.safe_string(params, 'account-category', accountCategory)
        params = self.omit(params, 'account-category')
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_string(account, 'id')
        request = {
            'account-group': accountGroup,
        }
        method = 'v1PrivateAccountCategoryGetBalance'
        if accountCategory == 'futures':
            method = 'v1PrivateAccountGroupGetFuturesCollateralBalance'
        else:
            request['account-category'] = accountCategory
        response = getattr(self, method)(self.extend(request, params))
        #
        # cash
        #
        #     {
        #         'code': 0,
        #         'data': [
        #             {
        #                 'asset': 'BCHSV',
        #                 'totalBalance': '64.298000048',
        #                 'availableBalance': '64.298000048',
        #             },
        #         ]
        #     }
        #
        # margin
        #
        #     {
        #         'code': 0,
        #         'data': [
        #             {
        #                 'asset': 'BCHSV',
        #                 'totalBalance': '64.298000048',
        #                 'availableBalance': '64.298000048',
        #                 'borrowed': '0',
        #                 'interest': '0',
        #             },
        #         ]
        #     }
        #
        # futures
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {"asset":"BTC","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"9456.59"},
        #             {"asset":"ETH","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"235.95"},
        #             {"asset":"USDT","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1"},
        #             {"asset":"USDC","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1.00035"},
        #             {"asset":"PAX","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1.00045"},
        #             {"asset":"USDTR","totalBalance":"0","availableBalance":"0","maxTransferrable":"0","priceInUSDT":"1"}
        #         ]
        #     }
        #
        return self.parse_balance(response)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.v1PublicGetDepth(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "data":{
        #             "m":"depth-snapshot",
        #             "symbol":"BTC-PERP",
        #             "data":{
        #                 "ts":1590223998202,
        #                 "seqnum":115444921,
        #                 "asks":[
        #                     ["9207.5","18.2383"],
        #                     ["9207.75","18.8235"],
        #                     ["9208","10.7873"],
        #                 ],
        #                 "bids":[
        #                     ["9207.25","0.4009"],
        #                     ["9207","0.003"],
        #                     ["9206.5","0.003"],
        #                 ]
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        orderbook = self.safe_value(data, 'data', {})
        timestamp = self.safe_integer(orderbook, 'ts')
        result = self.parse_order_book(orderbook, symbol, timestamp)
        result['nonce'] = self.safe_integer(orderbook, 'seqnum')
        return result

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "symbol":"QTUM/BTC",
        #         "open":"0.00016537",
        #         "close":"0.00019077",
        #         "high":"0.000192",
        #         "low":"0.00016537",
        #         "volume":"846.6",
        #         "ask":["0.00018698","26.2"],
        #         "bid":["0.00018408","503.7"],
        #         "type":"spot"
        #     }
        #
        timestamp = None
        marketId = self.safe_string(ticker, 'symbol')
        type = self.safe_string(ticker, 'type')
        delimiter = '/' if (type == 'spot') else None
        symbol = self.safe_symbol(marketId, market, delimiter)
        close = self.safe_number(ticker, 'close')
        bid = self.safe_value(ticker, 'bid', [])
        ask = self.safe_value(ticker, 'ask', [])
        open = self.safe_number(ticker, 'open')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': None,
            'high': self.safe_number(ticker, 'high'),
            'low': self.safe_number(ticker, 'low'),
            'bid': self.safe_number(bid, 0),
            'bidVolume': self.safe_number(bid, 1),
            'ask': self.safe_number(ask, 0),
            'askVolume': self.safe_number(ask, 1),
            'vwap': None,
            'open': open,
            'close': close,
            'last': close,
            'previousClose': None,  # previous day close
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_number(ticker, 'volume'),
            'quoteVolume': None,
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.v1PublicGetTicker(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "data":{
        #             "symbol":"BTC-PERP",  # or "BTC/USDT"
        #             "open":"9073",
        #             "close":"9185.75",
        #             "high":"9185.75",
        #             "low":"9185.75",
        #             "volume":"576.8334",
        #             "ask":["9185.75","15.5863"],
        #             "bid":["9185.5","0.003"],
        #             "type":"derivatives",  # or "spot"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.parse_ticker(data, market)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        request = {}
        if symbols is not None:
            marketIds = self.market_ids(symbols)
            request['symbol'] = ','.join(marketIds)
        response = self.v1PublicGetTicker(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "symbol":"QTUM/BTC",
        #                 "open":"0.00016537",
        #                 "close":"0.00019077",
        #                 "high":"0.000192",
        #                 "low":"0.00016537",
        #                 "volume":"846.6",
        #                 "ask":["0.00018698","26.2"],
        #                 "bid":["0.00018408","503.7"],
        #                 "type":"spot"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_tickers(data, symbols)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "m":"bar",
        #         "s":"BTC/USDT",
        #         "data":{
        #             "i":"1",
        #             "ts":1590228000000,
        #             "o":"9139.59",
        #             "c":"9131.94",
        #             "h":"9139.99",
        #             "l":"9121.71",
        #             "v":"25.20648"
        #         }
        #     }
        #
        data = self.safe_value(ohlcv, 'data', {})
        return [
            self.safe_integer(data, 'ts'),
            self.safe_number(data, 'o'),
            self.safe_number(data, 'h'),
            self.safe_number(data, 'l'),
            self.safe_number(data, 'c'),
            self.safe_number(data, 'v'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'interval': self.timeframes[timeframe],
        }
        # if since and limit are not specified
        # the exchange will return just 1 last candle by default
        duration = self.parse_timeframe(timeframe)
        options = self.safe_value(self.options, 'fetchOHLCV', {})
        defaultLimit = self.safe_integer(options, 'limit', 500)
        if since is not None:
            request['from'] = since
            if limit is None:
                limit = defaultLimit
            else:
                limit = min(limit, defaultLimit)
            request['to'] = self.sum(since, limit * duration * 1000, 1)
        elif limit is not None:
            request['n'] = limit  # max 500
        response = self.v1PublicGetBarhist(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "data":[
        #             {
        #                 "m":"bar",
        #                 "s":"BTC/USDT",
        #                 "data":{
        #                     "i":"1",
        #                     "ts":1590228000000,
        #                     "o":"9139.59",
        #                     "c":"9131.94",
        #                     "h":"9139.99",
        #                     "l":"9121.71",
        #                     "v":"25.20648"
        #                 }
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #     {
        #         "p":"9128.5",  # price
        #         "q":"0.0030",  # quantity
        #         "ts":1590229002385,  # timestamp
        #         "bm":false,  # if True, the buyer is the market maker, we only use self field to "define the side" of a public trade
        #         "seqnum":180143985289898554
        #     }
        #
        timestamp = self.safe_integer(trade, 'ts')
        priceString = self.safe_string_2(trade, 'price', 'p')
        amountString = self.safe_string(trade, 'q')
        buyerIsMaker = self.safe_value(trade, 'bm', False)
        makerOrTaker = 'maker' if buyerIsMaker else 'taker'
        side = 'buy' if buyerIsMaker else 'sell'
        symbol = None
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': None,
            'order': None,
            'type': None,
            'takerOrMaker': makerOrTaker,
            'side': side,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': None,
        }, market)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['n'] = limit  # max 100
        response = self.v1PublicGetTrades(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "data":{
        #             "m":"trades",
        #             "symbol":"BTC-PERP",
        #             "data":[
        #                 {"p":"9128.5","q":"0.0030","ts":1590229002385,"bm":false,"seqnum":180143985289898554},
        #                 {"p":"9129","q":"0.0030","ts":1590229002642,"bm":false,"seqnum":180143985289898587},
        #                 {"p":"9129.5","q":"0.0030","ts":1590229021306,"bm":false,"seqnum":180143985289899043}
        #             ]
        #         }
        #     }
        #
        records = self.safe_value(response, 'data', [])
        trades = self.safe_value(records, 'data', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_order_status(self, status):
        statuses = {
            'PendingNew': 'open',
            'New': 'open',
            'PartiallyFilled': 'open',
            'Filled': 'closed',
            'Canceled': 'canceled',
            'Rejected': 'rejected',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "id": "16e607e2b83a8bXHbAwwoqDo55c166fa",
        #         "orderId": "16e85b4d9b9a8bXHbAwwoqDoc3d66830",
        #         "orderType": "Market",
        #         "symbol": "BTC/USDT",
        #         "timestamp": 1573576916201
        #     }
        #
        #     {
        #         "ac": "FUTURES",
        #         "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #         "time": 1640819389454,
        #         "orderId": "a17e0874ecbdU0711043490bbtcpDU5X",
        #         "seqNum": -1,
        #         "orderType": "Limit",
        #         "execInst": "NULL_VAL",
        #         "side": "Buy",
        #         "symbol": "BTC-PERP",
        #         "price": "30000",
        #         "orderQty": "0.002",
        #         "stopPrice": "0",
        #         "stopBy": "ref-px",
        #         "status": "Ack",
        #         "lastExecTime": 1640819389454,
        #         "lastQty": "0",
        #         "lastPx": "0",
        #         "avgFilledPx": "0",
        #         "cumFilledQty": "0",
        #         "fee": "0",
        #         "cumFee": "0",
        #         "feeAsset": "",
        #         "errorCode": "",
        #         "posStopLossPrice": "0",
        #         "posStopLossTrigger": "market",
        #         "posTakeProfitPrice": "0",
        #         "posTakeProfitTrigger": "market",
        #         "liquidityInd": "n"
        #      }
        #
        # fetchOrder, fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "symbol":       "BTC/USDT",
        #         "price":        "8131.22",
        #         "orderQty":     "0.00082",
        #         "orderType":    "Market",
        #         "avgPx":        "7392.02",
        #         "cumFee":       "0.005152238",
        #         "cumFilledQty": "0.00082",
        #         "errorCode":    "",
        #         "feeAsset":     "USDT",
        #         "lastExecTime": 1575953151764,
        #         "orderId":      "a16eee20b6750866943712zWEDdAjt3",
        #         "seqNum":       2623469,
        #         "side":         "Buy",
        #         "status":       "Filled",
        #         "stopPrice":    "",
        #         "execInst":     "NULL_VAL"
        #     }
        #
        #     {
        #         "ac": "FUTURES",
        #         "accountId": "testabcdefg",
        #         "avgPx": "0",
        #         "cumFee": "0",
        #         "cumQty": "0",
        #         "errorCode": "NULL_VAL",
        #         "execInst": "NULL_VAL",
        #         "feeAsset": "USDT",
        #         "lastExecTime": 1584072844085,
        #         "orderId": "r170d21956dd5450276356bbtcpKa74",
        #         "orderQty": "1.1499",
        #         "orderType": "Limit",
        #         "price": "4000",
        #         "sendingTime": 1584072841033,
        #         "seqNum": 24105338,
        #         "side": "Buy",
        #         "status": "Canceled",
        #         "stopPrice": "",
        #         "symbol": "BTC-PERP"
        #     },
        #
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market, '/')
        timestamp = self.safe_integer_2(order, 'timestamp', 'sendingTime')
        lastTradeTimestamp = self.safe_integer(order, 'lastExecTime')
        price = self.safe_string(order, 'price')
        amount = self.safe_string(order, 'orderQty')
        average = self.safe_string(order, 'avgPx')
        filled = self.safe_string_2(order, 'cumFilledQty', 'cumQty')
        id = self.safe_string(order, 'orderId')
        clientOrderId = self.safe_string(order, 'id')
        if clientOrderId is not None:
            if len(clientOrderId) < 1:
                clientOrderId = None
        type = self.safe_string_lower(order, 'orderType')
        side = self.safe_string_lower(order, 'side')
        feeCost = self.safe_number(order, 'cumFee')
        fee = None
        if feeCost is not None:
            feeCurrencyId = self.safe_string(order, 'feeAsset')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        stopPrice = self.safe_number(order, 'stopPrice')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': None,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': fee,
            'trades': None,
        }, market)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        self.load_accounts()
        market = self.market(symbol)
        style, query = self.handle_market_type_and_params('createOrder', market, params)
        options = self.safe_value(self.options, 'createOrder', {})
        accountCategories = self.safe_value(self.options, 'accountCategories', {})
        accountCategory = self.safe_string(accountCategories, style, 'cash')
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_value(account, 'id')
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'id')
        request = {
            'account-group': accountGroup,
            'account-category': accountCategory,
            'symbol': market['id'],
            'time': self.milliseconds(),
            'orderQty': self.amount_to_precision(symbol, amount),
            'orderType': type,  # "limit", "market", "stop_market", "stop_limit"
            'side': side,  # "buy" or "sell"
            # 'orderPrice': self.price_to_precision(symbol, price),
            # 'stopPrice': self.price_to_precision(symbol, stopPrice),  # required for stop orders
            # 'postOnly': 'false',  # 'false', 'true'
            # 'timeInForce': 'GTC',  # GTC, IOC, FOK
            # 'respInst': 'ACK',  # ACK, 'ACCEPT, DONE
        }
        if clientOrderId is not None:
            request['id'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'id'])
        if (type == 'limit') or (type == 'stop_limit'):
            request['orderPrice'] = self.price_to_precision(symbol, price)
        if (type == 'stop_limit') or (type == 'stop_market'):
            stopPrice = self.safe_number(params, 'stopPrice')
            if stopPrice is None:
                raise InvalidOrder(self.id + ' createOrder() requires a stopPrice parameter for ' + type + ' orders')
            else:
                request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
                params = self.omit(params, 'stopPrice')
        defaultMethod = self.safe_string(options, 'method', 'v1PrivateAccountCategoryPostOrder')
        method = self.get_supported_mapping(style, {
            'spot': defaultMethod,
            'margin': defaultMethod,
            'swap': 'v2PrivateAccountGroupPostFuturesOrder',
        })
        if method == 'v1PrivateAccountCategoryPostOrder':
            if accountCategory is not None:
                request['category'] = accountCategory
        else:
            request['account-category'] = accountCategory
        response = getattr(self, method)(self.extend(request, query))
        #
        # AccountCategoryPostOrder
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "ac": "MARGIN",
        #             "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #             "action": "place-order",
        #             "info": {
        #                 "id": "16e607e2b83a8bXHbAwwoqDo55c166fa",
        #                 "orderId": "16e85b4d9b9a8bXHbAwwoqDoc3d66830",
        #                 "orderType": "Market",
        #                 "symbol": "BTC/USDT",
        #                 "timestamp": 1573576916201
        #             },
        #             "status": "Ack"
        #         }
        #     }
        #
        # AccountGroupPostFuturesOrder
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "meta": {
        #                 "id": "",
        #                 "action": "place-order",
        #                 "respInst": "ACK"
        #             },
        #             "order": {
        #                 "ac": "FUTURES",
        #                 "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #                 "time": 1640819389454,
        #                 "orderId": "a17e0874ecbdU0711043490bbtcpDU5X",
        #                 "seqNum": -1,
        #                 "orderType": "Limit",
        #                 "execInst": "NULL_VAL",
        #                 "side": "Buy",
        #                 "symbol": "BTC-PERP",
        #                 "price": "30000",
        #                 "orderQty": "0.002",
        #                 "stopPrice": "0",
        #                 "stopBy": "ref-px",
        #                 "status": "Ack",
        #                 "lastExecTime": 1640819389454,
        #                 "lastQty": "0",
        #                 "lastPx": "0",
        #                 "avgFilledPx": "0",
        #                 "cumFilledQty": "0",
        #                 "fee": "0",
        #                 "cumFee": "0",
        #                 "feeAsset": "",
        #                 "errorCode": "",
        #                 "posStopLossPrice": "0",
        #                 "posStopLossTrigger": "market",
        #                 "posTakeProfitPrice": "0",
        #                 "posTakeProfitTrigger": "market",
        #                 "liquidityInd": "n"
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        order = self.safe_value_2(data, 'order', 'info', {})
        return self.parse_order(order, market)

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        self.load_accounts()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        type, query = self.handle_market_type_and_params('fetchOrder', market, params)
        options = self.safe_value(self.options, 'fetchOrder', {})
        accountCategories = self.safe_value(self.options, 'accountCategories', {})
        accountCategory = self.safe_string(accountCategories, type, 'cash')
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_value(account, 'id')
        request = {
            'account-group': accountGroup,
            'account-category': accountCategory,
            'orderId': id,
        }
        defaultMethod = self.safe_string(options, 'method', 'v1PrivateAccountCategoryGetOrderStatus')
        method = self.get_supported_mapping(type, {
            'spot': defaultMethod,
            'margin': defaultMethod,
            'swap': 'v2PrivateAccountGroupGetFuturesOrderStatus',
        })
        if method == 'v1PrivateAccountCategoryGetOrderStatus':
            if accountCategory is not None:
                request['category'] = accountCategory
        else:
            request['account-category'] = accountCategory
        response = getattr(self, method)(self.extend(request, query))
        #
        # AccountCategoryGetOrderStatus
        #
        #     {
        #         "code": 0,
        #         "accountCategory": "CASH",
        #         "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #         "data": [
        #             {
        #                 "symbol":       "BTC/USDT",
        #                 "price":        "8131.22",
        #                 "orderQty":     "0.00082",
        #                 "orderType":    "Market",
        #                 "avgPx":        "7392.02",
        #                 "cumFee":       "0.005152238",
        #                 "cumFilledQty": "0.00082",
        #                 "errorCode":    "",
        #                 "feeAsset":     "USDT",
        #                 "lastExecTime": 1575953151764,
        #                 "orderId":      "a16eee20b6750866943712zWEDdAjt3",
        #                 "seqNum":       2623469,
        #                 "side":         "Buy",
        #                 "status":       "Filled",
        #                 "stopPrice":    "",
        #                 "execInst":     "NULL_VAL"
        #             }
        #         ]
        #     }
        #
        # AccountGroupGetFuturesOrderStatus
        #
        #     {
        #         "code": 0,
        #         "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #         "ac": "FUTURES",
        #         "data": {
        #             "ac": "FUTURES",
        #             "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #             "time": 1640247020217,
        #             "orderId": "r17de65747aeU0711043490bbtcp0cmt",
        #             "seqNum": 28796162908,
        #             "orderType": "Limit",
        #             "execInst": "NULL_VAL",
        #             "side": "Buy",
        #             "symbol": "BTC-PERP",
        #             "price": "30000",
        #             "orderQty": "0.0021",
        #             "stopPrice": "0",
        #             "stopBy": "market",
        #             "status": "New",
        #             "lastExecTime": 1640247020232,
        #             "lastQty": "0",
        #             "lastPx": "0",
        #             "avgFilledPx": "0",
        #             "cumFilledQty": "0",
        #             "fee": "0",
        #             "cumFee": "0",
        #             "feeAsset": "USDT",
        #             "errorCode": "",
        #             "posStopLossPrice": "0",
        #             "posStopLossTrigger": "market",
        #             "posTakeProfitPrice": "0",
        #             "posTakeProfitTrigger": "market",
        #             "liquidityInd": "n"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.parse_order(data, market)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        self.load_accounts()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_value(account, 'id')
        type, query = self.handle_market_type_and_params('fetchOpenOrders', market, params)
        accountCategories = self.safe_value(self.options, 'accountCategories', {})
        accountCategory = self.safe_string(accountCategories, type, 'cash')
        request = {
            'account-group': accountGroup,
            'account-category': accountCategory,
        }
        options = self.safe_value(self.options, 'fetchOpenOrders', {})
        defaultMethod = self.safe_string(options, 'method', 'v1PrivateAccountCategoryGetOrderOpen')
        method = self.get_supported_mapping(type, {
            'spot': defaultMethod,
            'margin': defaultMethod,
            'swap': 'v2PrivateAccountGroupGetFuturesOrderOpen',
        })
        if method == 'v1PrivateAccountCategoryGetOrderOpen':
            if accountCategory is not None:
                request['category'] = accountCategory
        else:
            request['account-category'] = accountCategory
        response = getattr(self, method)(self.extend(request, query))
        #
        # AccountCategoryGetOrderOpen
        #
        #     {
        #         "ac": "CASH",
        #         "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #         "code": 0,
        #         "data": [
        #             {
        #                 "avgPx": "0",         # Average filled price of the order
        #                 "cumFee": "0",       # cumulative fee paid for self order
        #                 "cumFilledQty": "0",  # cumulative filled quantity
        #                 "errorCode": "",     # error code; could be empty
        #                 "feeAsset": "USDT",  # fee asset
        #                 "lastExecTime": 1576019723550,  #  The last execution time of the order
        #                 "orderId": "s16ef21882ea0866943712034f36d83",  # server provided orderId
        #                 "orderQty": "0.0083",  # order quantity
        #                 "orderType": "Limit",  # order type
        #                 "price": "7105",       # order price
        #                 "seqNum": 8193258,     # sequence number
        #                 "side": "Buy",         # order side
        #                 "status": "New",       # order status on matching engine
        #                 "stopPrice": "",       # only available for stop market and stop limit orders; otherwise empty
        #                 "symbol": "BTC/USDT",
        #                 "execInst": "NULL_VAL"  # execution instruction
        #             },
        #         ]
        #     }
        #
        # AccountGroupGetFuturesOrderOpen
        #
        # {
        #     "code": 0,
        #     "data": [
        #         {
        #             "ac": "FUTURES",
        #             "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #             "time": 1640247020217,
        #             "orderId": "r17de65747aeU0711043490bbtcp0cmt",
        #             "seqNum": 28796162908,
        #             "orderType": "Limit",
        #             "execInst": "NULL_VAL",
        #             "side": "Buy",
        #             "symbol": "BTC-PERP",
        #             "price": "30000",
        #             "orderQty": "0.0021",
        #             "stopPrice": "0",
        #             "stopBy": "market",
        #             "status": "New",
        #             "lastExecTime": 1640247020232,
        #             "lastQty": "0",
        #             "lastPx": "0",
        #             "avgFilledPx": "0",
        #             "cumFilledQty": "0",
        #             "fee": "0",
        #             "cumFee": "0",
        #             "feeAsset": "USDT",
        #             "errorCode": "",
        #             "posStopLossPrice": "0",
        #             "posStopLossTrigger": "market",
        #             "posTakeProfitPrice": "0",
        #             "posTakeProfitTrigger": "market",
        #             "liquidityInd": "n"
        #         }
        #     ]
        # }
        #
        data = self.safe_value(response, 'data', [])
        if accountCategory == 'futures':
            return self.parse_orders(data, market, since, limit)
        # a workaround for https://github.com/ccxt/ccxt/issues/7187
        orders = []
        for i in range(0, len(data)):
            order = self.parse_order(data[i], market)
            orders.append(order)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        self.load_accounts()
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_value(account, 'id')
        request = {
            'account-group': accountGroup,
            # 'category': accountCategory,
            # 'symbol': market['id'],
            # 'orderType': 'market',  # optional, string
            # 'side': 'buy',  # or 'sell', optional, case insensitive.
            # 'status': 'Filled',  # "Filled", "Canceled", or "Rejected"
            # 'startTime': exchange.milliseconds(),
            # 'endTime': exchange.milliseconds(),
            # 'page': 1,
            # 'pageSize': 100,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        type, query = self.handle_market_type_and_params('fetchCLosedOrders', market, params)
        options = self.safe_value(self.options, 'fetchClosedOrders', {})
        defaultMethod = self.safe_string(options, 'method', 'v1PrivateAccountGroupGetOrderHist')
        method = self.get_supported_mapping(type, {
            'spot': defaultMethod,
            'margin': defaultMethod,
            'swap': 'v2PrivateAccountGroupGetFuturesOrderHistCurrent',
        })
        accountCategories = self.safe_value(self.options, 'accountCategories', {})
        accountCategory = self.safe_string(accountCategories, type, 'cash')
        if method == 'v1PrivateAccountGroupGetOrderHist':
            if accountCategory is not None:
                request['category'] = accountCategory
        else:
            request['account-category'] = accountCategory
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['pageSize'] = limit
        response = getattr(self, method)(self.extend(request, query))
        #
        # accountCategoryGetOrderHistCurrent
        #
        #     {
        #         "code":0,
        #         "accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda",
        #         "ac":"CASH",
        #         "data":[
        #             {
        #                 "seqNum":15561826728,
        #                 "orderId":"a17294d305c0U6491137460bethu7kw9",
        #                 "symbol":"ETH/USDT",
        #                 "orderType":"Limit",
        #                 "lastExecTime":1591635618200,
        #                 "price":"200",
        #                 "orderQty":"0.1",
        #                 "side":"Buy",
        #                 "status":"Canceled",
        #                 "avgPx":"0",
        #                 "cumFilledQty":"0",
        #                 "stopPrice":"",
        #                 "errorCode":"",
        #                 "cumFee":"0",
        #                 "feeAsset":"USDT",
        #                 "execInst":"NULL_VAL"
        #             }
        #         ]
        #     }
        #
        # accountGroupGetOrderHist
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "data": [
        #                 {
        #                     "ac": "FUTURES",
        #                     "accountId": "testabcdefg",
        #                     "avgPx": "0",
        #                     "cumFee": "0",
        #                     "cumQty": "0",
        #                     "errorCode": "NULL_VAL",
        #                     "execInst": "NULL_VAL",
        #                     "feeAsset": "USDT",
        #                     "lastExecTime": 1584072844085,
        #                     "orderId": "r170d21956dd5450276356bbtcpKa74",
        #                     "orderQty": "1.1499",
        #                     "orderType": "Limit",
        #                     "price": "4000",
        #                     "sendingTime": 1584072841033,
        #                     "seqNum": 24105338,
        #                     "side": "Buy",
        #                     "status": "Canceled",
        #                     "stopPrice": "",
        #                     "symbol": "BTC-PERP"
        #                 },
        #             ],
        #             "hasNext": False,
        #             "limit": 500,
        #             "page": 1,
        #             "pageSize": 20
        #         }
        #     }
        #
        # accountGroupGetFuturesOrderHistCurrent
        #
        #     {
        #         "code": 0,
        #         "data": [
        #             {
        #                 "ac": "FUTURES",
        #                 "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #                 "time": 1640245777002,
        #                 "orderId": "r17de6444fa6U0711043490bbtcpJ2lI",
        #                 "seqNum": 28796124902,
        #                 "orderType": "Limit",
        #                 "execInst": "NULL_VAL",
        #                 "side": "Buy",
        #                 "symbol": "BTC-PERP",
        #                 "price": "30000",
        #                 "orderQty": "0.0021",
        #                 "stopPrice": "0",
        #                 "stopBy": "market",
        #                 "status": "Canceled",
        #                 "lastExecTime": 1640246574886,
        #                 "lastQty": "0",
        #                 "lastPx": "0",
        #                 "avgFilledPx": "0",
        #                 "cumFilledQty": "0",
        #                 "fee": "0",
        #                 "cumFee": "0",
        #                 "feeAsset": "USDT",
        #                 "errorCode": "",
        #                 "posStopLossPrice": "0",
        #                 "posStopLossTrigger": "market",
        #                 "posTakeProfitPrice": "0",
        #                 "posTakeProfitTrigger": "market",
        #                 "liquidityInd": "n"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data')
        isArray = isinstance(data, list)
        if not isArray:
            data = self.safe_value(data, 'data', [])
        return self.parse_orders(data, market, since, limit)

    def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        self.load_accounts()
        market = self.market(symbol)
        type, query = self.handle_market_type_and_params('cancelOrder', market, params)
        options = self.safe_value(self.options, 'cancelOrder', {})
        accountCategories = self.safe_value(self.options, 'accountCategories', {})
        accountCategory = self.safe_string(accountCategories, type, 'cash')
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_value(account, 'id')
        request = {
            'account-group': accountGroup,
            'account-category': accountCategory,
            'symbol': market['id'],
            'time': self.milliseconds(),
            'id': 'foobar',
        }
        defaultMethod = self.safe_string(options, 'method', 'v1PrivateAccountCategoryDeleteOrder')
        method = self.get_supported_mapping(type, {
            'spot': defaultMethod,
            'margin': defaultMethod,
            'swap': 'v2PrivateAccountGroupDeleteFuturesOrder',
        })
        if method == 'v1PrivateAccountCategoryDeleteOrder':
            if accountCategory is not None:
                request['category'] = accountCategory
        else:
            request['account-category'] = accountCategory
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'id')
        if clientOrderId is None:
            request['orderId'] = id
        else:
            request['id'] = clientOrderId
            params = self.omit(params, ['clientOrderId', 'id'])
        response = getattr(self, method)(self.extend(request, query))
        #
        # AccountCategoryDeleteOrder
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #             "ac": "CASH",
        #             "action": "cancel-order",
        #             "status": "Ack",
        #             "info": {
        #                 "id":        "wv8QGquoeamhssvQBeHOHGQCGlcBjj23",
        #                 "orderId":   "16e6198afb4s8bXHbAwwoqDo2ebc19dc",
        #                 "orderType": "",  # could be empty
        #                 "symbol":    "ETH/USDT",
        #                 "timestamp":  1573594877822
        #             }
        #         }
        #     }
        #
        # AccountGroupDeleteFuturesOrder
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "meta": {
        #                 "id": "foobar",
        #                 "action": "cancel-order",
        #                 "respInst": "ACK"
        #             },
        #             "order": {
        #                 "ac": "FUTURES",
        #                 "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #                 "time": 1640244480476,
        #                 "orderId": "r17de63086f4U0711043490bbtcpPUF4",
        #                 "seqNum": 28795959269,
        #                 "orderType": "Limit",
        #                 "execInst": "NULL_VAL",
        #                 "side": "Buy",
        #                 "symbol": "BTC-PERP",
        #                 "price": "30000",
        #                 "orderQty": "0.0021",
        #                 "stopPrice": "0",
        #                 "stopBy": "market",
        #                 "status": "New",
        #                 "lastExecTime": 1640244480491,
        #                 "lastQty": "0",
        #                 "lastPx": "0",
        #                 "avgFilledPx": "0",
        #                 "cumFilledQty": "0",
        #                 "fee": "0",
        #                 "cumFee": "0",
        #                 "feeAsset": "BTCPC",
        #                 "errorCode": "",
        #                 "posStopLossPrice": "0",
        #                 "posStopLossTrigger": "market",
        #                 "posTakeProfitPrice": "0",
        #                 "posTakeProfitTrigger": "market",
        #                 "liquidityInd": "n"
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        order = self.safe_value_2(data, 'order', 'info', {})
        return self.parse_order(order, market)

    def cancel_all_orders(self, symbol=None, params={}):
        self.load_markets()
        self.load_accounts()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        type, query = self.handle_market_type_and_params('cancelAllOrders', market, params)
        options = self.safe_value(self.options, 'cancelAllOrders', {})
        accountCategories = self.safe_value(self.options, 'accountCategories', {})
        accountCategory = self.safe_string(accountCategories, type, 'cash')
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_value(account, 'id')
        request = {
            'account-group': accountGroup,
            'account-category': accountCategory,
            'time': self.milliseconds(),
        }
        if symbol is not None:
            request['symbol'] = market['id']
        defaultMethod = self.safe_string(options, 'method', 'v1PrivateAccountCategoryDeleteOrderAll')
        method = self.get_supported_mapping(type, {
            'spot': defaultMethod,
            'margin': defaultMethod,
            'swap': 'v2PrivateAccountGroupDeleteFuturesOrderAll',
        })
        if method == 'v1PrivateAccountCategoryDeleteOrderAll':
            if accountCategory is not None:
                request['category'] = accountCategory
        else:
            request['account-category'] = accountCategory
        response = getattr(self, method)(self.extend(request, query))
        #
        # AccountCategoryDeleteOrderAll
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "ac": "CASH",
        #             "accountId": "cshQtyfq8XLAA9kcf19h8bXHbAwwoqDo",
        #             "action": "cancel-all",
        #             "info": {
        #                 "id":  "2bmYvi7lyTrneMzpcJcf2D7Pe9V1P9wy",
        #                 "orderId": "",
        #                 "orderType": "NULL_VAL",
        #                 "symbol": "",
        #                 "timestamp": 1574118495462
        #             },
        #             "status": "Ack"
        #         }
        #     }
        #
        # AccountGroupDeleteFuturesOrderAll
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "ac": "FUTURES",
        #             "accountId": "fut2ODPhGiY71Pl4vtXnOZ00ssgD7QGn",
        #             "action": "cancel-all",
        #             "info": {
        #                 "symbol":"BTC-PERP"
        #             }
        #         }
        #     }
        #
        return response

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     {
        #         address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
        #         destTag: "",
        #         tagType: "",
        #         tagId: "",
        #         chainName: "ERC20",
        #         numConfirmations: 20,
        #         withdrawalFee: 1,
        #         nativeScale: 4,
        #         tips: []
        #     }
        #
        address = self.safe_string(depositAddress, 'address')
        tagId = self.safe_string(depositAddress, 'tagId')
        tag = self.safe_string(depositAddress, tagId)
        self.check_address(address)
        code = None if (currency is None) else currency['code']
        chainName = self.safe_string(depositAddress, 'chainName')
        network = self.safe_network(chainName)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': network,
            'info': depositAddress,
        }

    def safe_network(self, networkId):
        networksById = {
            'TRC20': 'TRC20',
            'ERC20': 'ERC20',
            'GO20': 'GO20',
            'BEP2': 'BEP2',
            'BEP20(BSC)': 'BEP20',
            'Bitcoin': 'BTC',
            'Bitcoin ABC': 'BCH',
            'Litecoin': 'LTC',
            'Matic Network': 'MATIC',
            'Solana': 'SOL',
            'xDai': 'STAKE',
            'Akash': 'AKT',
        }
        return self.safe_string(networksById, networkId, networkId)

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        chainName = self.safe_string(params, 'chainName')
        params = self.omit(params, 'chainName')
        request = {
            'asset': currency['id'],
        }
        response = self.v1PrivateGetWalletDepositAddress(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "data":{
        #             "asset":"USDT",
        #             "assetName":"Tether",
        #             "address":[
        #                 {
        #                     "address":"1N22odLHXnLPCjC8kwBJPTayarr9RtPod6",
        #                     "destTag":"",
        #                     "tagType":"",
        #                     "tagId":"",
        #                     "chainName":"Omni",
        #                     "numConfirmations":3,
        #                     "withdrawalFee":4.7,
        #                     "nativeScale":4,
        #                     "tips":[]
        #                 },
        #                 {
        #                     "address":"0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
        #                     "destTag":"",
        #                     "tagType":"",
        #                     "tagId":"",
        #                     "chainName":"ERC20",
        #                     "numConfirmations":20,
        #                     "withdrawalFee":1.0,
        #                     "nativeScale":4,
        #                     "tips":[]
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        addresses = self.safe_value(data, 'address', [])
        numAddresses = len(addresses)
        address = None
        if numAddresses > 1:
            addressesByChainName = self.index_by(addresses, 'chainName')
            if chainName is None:
                chainNames = list(addressesByChainName.keys())
                chains = ', '.join(chainNames)
                raise ArgumentsRequired(self.id + ' fetchDepositAddress returned more than one address, a chainName parameter is required, one of ' + chains)
            address = self.safe_value(addressesByChainName, chainName, {})
        else:
            # first address
            address = self.safe_value(addresses, 0, {})
        result = self.parse_deposit_address(address, currency)
        return self.extend(result, {
            'info': response,
        })

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        request = {
            'txType': 'deposit',
        }
        return self.fetch_transactions(code, since, limit, self.extend(request, params))

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        request = {
            'txType': 'withdrawal',
        }
        return self.fetch_transactions(code, since, limit, self.extend(request, params))

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'asset': currency['id'],
            # 'page': 1,
            # 'pageSize': 20,
            # 'startTs': self.milliseconds(),
            # 'endTs': self.milliseconds(),
            # 'txType': undefned,  # deposit, withdrawal
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['asset'] = currency['id']
        if since is not None:
            request['startTs'] = since
        if limit is not None:
            request['pageSize'] = limit
        response = self.v1PrivateGetWalletTransactions(self.extend(request, params))
        #
        #     {
        #         code: 0,
        #         data: {
        #             data: [
        #                 {
        #                     requestId: "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB",
        #                     time: 1591606166000,
        #                     asset: "USDT",
        #                     transactionType: "deposit",
        #                     amount: "25",
        #                     commission: "0",
        #                     networkTransactionId: "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce",
        #                     status: "pending",
        #                     numConfirmed: 8,
        #                     numConfirmations: 20,
        #                     destAddress: {address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722"}
        #                 }
        #             ],
        #             page: 1,
        #             pageSize: 20,
        #             hasNext: False
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        transactions = self.safe_value(data, 'data', [])
        return self.parse_transactions(transactions, currency, since, limit)

    def parse_transaction_status(self, status):
        statuses = {
            'reviewing': 'pending',
            'pending': 'pending',
            'confirmed': 'ok',
            'rejected': 'rejected',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        #     {
        #         requestId: "wuzd1Ojsqtz4bCA3UXwtUnnJDmU8PiyB",
        #         time: 1591606166000,
        #         asset: "USDT",
        #         transactionType: "deposit",
        #         amount: "25",
        #         commission: "0",
        #         networkTransactionId: "0xbc4eabdce92f14dbcc01d799a5f8ca1f02f4a3a804b6350ea202be4d3c738fce",
        #         status: "pending",
        #         numConfirmed: 8,
        #         numConfirmations: 20,
        #         destAddress: {
        #             address: "0xe7c70b4e73b6b450ee46c3b5c0f5fb127ca55722",
        #             destTag: "..."  # for currencies that have it
        #         }
        #     }
        #
        id = self.safe_string(transaction, 'requestId')
        amount = self.safe_number(transaction, 'amount')
        destAddress = self.safe_value(transaction, 'destAddress', {})
        address = self.safe_string(destAddress, 'address')
        tag = self.safe_string(destAddress, 'destTag')
        txid = self.safe_string(transaction, 'networkTransactionId')
        type = self.safe_string(transaction, 'transactionType')
        timestamp = self.safe_integer(transaction, 'time')
        currencyId = self.safe_string(transaction, 'asset')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        feeCost = self.safe_number(transaction, 'commission')
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'network': None,
            'address': address,
            'addressTo': address,
            'addressFrom': None,
            'tag': tag,
            'tagTo': tag,
            'tagFrom': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': {
                'currency': code,
                'cost': feeCost,
            },
        }

    def fetch_positions(self, symbols=None, params={}):
        self.load_markets()
        self.load_accounts()
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_string(account, 'id')
        request = {
            'account-group': accountGroup,
        }
        return self.v2PrivateAccountGroupGetFuturesPosition(self.extend(request, params))

    def parse_funding_rate(self, fundingRate, market=None):
        #
        #      {
        #          "time": 1640061364830,
        #          "symbol": "EOS-PERP",
        #          "markPrice": "3.353854865",
        #          "indexPrice": "3.3542",
        #          "openInterest": "14242",
        #          "fundingRate": "-0.000073026",
        #          "nextFundingTime": 1640073600000
        #      }
        #
        marketId = self.safe_string(fundingRate, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        currentTime = self.safe_integer(fundingRate, 'time')
        nextFundingRate = self.safe_number(fundingRate, 'fundingRate')
        nextFundingRateTimestamp = self.safe_integer(fundingRate, 'nextFundingTime')
        previousFundingTimestamp = None
        return {
            'info': fundingRate,
            'symbol': symbol,
            'markPrice': self.safe_number(fundingRate, 'markPrice'),
            'indexPrice': self.safe_number(fundingRate, 'indexPrice'),
            'interestRate': self.parse_number('0'),
            'estimatedSettlePrice': None,
            'timestamp': currentTime,
            'datetime': self.iso8601(currentTime),
            'previousFundingRate': None,
            'nextFundingRate': nextFundingRate,
            'previousFundingTimestamp': previousFundingTimestamp,
            'nextFundingTimestamp': nextFundingRateTimestamp,
            'previousFundingDatetime': self.iso8601(previousFundingTimestamp),
            'nextFundingDatetime': self.iso8601(nextFundingRateTimestamp),
        }

    def fetch_funding_rates(self, symbols, params={}):
        self.load_markets()
        response = self.v2PublicGetFuturesPricingData(params)
        #
        #     {
        #          "code": 0,
        #          "data": {
        #              "contracts": [
        #                  {
        #                      "time": 1640061364830,
        #                      "symbol": "EOS-PERP",
        #                      "markPrice": "3.353854865",
        #                      "indexPrice": "3.3542",
        #                      "openInterest": "14242",
        #                      "fundingRate": "-0.000073026",
        #                      "nextFundingTime": 1640073600000
        #                  },
        #              ],
        #              "collaterals": [
        #                  {
        #                      "asset": "USDTR",
        #                      "referencePrice": "1"
        #                  },
        #              ]
        #          }
        #      }
        #
        data = self.safe_value(response, 'data', {})
        contracts = self.safe_value(data, 'contracts', [])
        result = self.parse_funding_rates(contracts)
        return self.filter_by_array(result, 'symbol', symbols)

    def set_leverage(self, leverage, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
        if (leverage < 1) or (leverage > 100):
            raise BadRequest(self.id + ' leverage should be between 1 and 100')
        self.load_markets()
        self.load_accounts()
        market = self.market(symbol)
        if market['type'] != 'future':
            raise BadSymbol(self.id + ' setLeverage() supports futures contracts only')
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_string(account, 'id')
        request = {
            'account-group': accountGroup,
            'symbol': market['id'],
            'leverage': leverage,
        }
        return self.v2PrivateAccountGroupPostFuturesLeverage(self.extend(request, params))

    def set_margin_mode(self, marginType, symbol=None, params={}):
        if marginType != 'isolated' and marginType != 'crossed':
            raise BadRequest(self.id + ' setMarginMode() marginType argument should be isolated or crossed')
        self.load_markets()
        self.load_accounts()
        market = self.market(symbol)
        account = self.safe_value(self.accounts, 0, {})
        accountGroup = self.safe_string(account, 'id')
        request = {
            'account-group': accountGroup,
            'symbol': market['id'],
            'marginType': marginType,
        }
        if market['type'] != 'future':
            raise BadSymbol(self.id + ' setMarginMode() supports futures contracts only')
        return self.v2PrivateAccountGroupPostFuturesMarginType(self.extend(request, params))

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        version = api[0]
        access = api[1]
        type = self.safe_string(api, 2)
        url = ''
        accountCategory = (type == 'accountCategory')
        if accountCategory or (type == 'accountGroup'):
            url += self.implode_params('/{account-group}', params)
            params = self.omit(params, 'account-group')
        request = self.implode_params(path, params)
        url += '/api/pro/'
        if version == 'v2':
            request = version + '/' + request
        else:
            url += version + '/'
        if accountCategory:
            url += self.implode_params('{account-category}/', params)
        params = self.omit(params, 'account-category')
        url += request
        if (version == 'v1') and (request == 'cash/balance') or (request == 'margin/balance'):
            request = 'balance'
        if request.find('subuser') >= 0:
            parts = request.split('/')
            request = parts[2]
        params = self.omit(params, self.extract_params(path))
        if access == 'public':
            if params:
                url += '?' + self.urlencode(params)
        else:
            self.check_required_credentials()
            timestamp = str(self.milliseconds())
            payload = timestamp + '+' + request
            hmac = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
            headers = {
                'x-auth-key': self.apiKey,
                'x-auth-timestamp': timestamp,
                'x-auth-signature': hmac,
            }
            if method == 'GET':
                if params:
                    url += '?' + self.urlencode(params)
            else:
                headers['Content-Type'] = 'application/json'
                body = self.json(params)
        url = self.urls['api'] + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        #
        #     {'code': 6010, 'message': 'Not enough balance.'}
        #     {'code': 60060, 'message': 'The order is already filled or canceled.'}
        #     {"code":2100,"message":"ApiKeyFailure"}
        #     {"code":300001,"message":"Price is too low from market price.","reason":"INVALID_PRICE","accountId":"cshrHKLZCjlZ2ejqkmvIHHtPmLYqdnda","ac":"CASH","action":"place-order","status":"Err","info":{"symbol":"BTC/USDT"}}
        #
        code = self.safe_string(response, 'code')
        message = self.safe_string(response, 'message')
        error = (code is not None) and (code != '0')
        if error or (message is not None):
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
