# -*- 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 BadResponse
from ccxt.base.errors import NullResponse
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class hbtc(Exchange):

    def describe(self):
        return self.deep_extend(super(hbtc, self).describe(), {
            'id': 'hbtc',
            'name': 'HBTC',
            'countries': ['CN'],
            'rateLimit': 2000,
            'version': 'v1',
            'has': {
                'cancelOrder': True,
                'CORS': False,
                'createOrder': True,
                'fetchAccounts': True,
                'fetchBalance': True,
                'fetchBidAsk': True,
                'fetchBidsAsks': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': False,
                'fetchDepositAddress': False,
                'fetchDeposits': True,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingLimits': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '2h': '2h',
                '4h': '4h',
                '6h': '6h',
                '8h': '8h',
                '12h': '12h',
                '1d': '1d',
                '3d': '3d',
                '1w': '1w',
                '1M': '1M',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/80134449-70663300-85a7-11ea-8942-e204cdeaab5d.jpg',  # 交易所LOGO
                'api': {
                    'quote': 'https://api.hbtc.com/openapi/quote',  # 市场API数据端点
                    'contract': 'https://api.hbtc.com/openapi/contract',  # 合约API数据端点
                    'option': 'https://api.hbtc.com/openapi/option',  # 合约API数据端点
                    'public': 'https://api.hbtc.com/openapi',  # 公共API数据端点
                    'private': 'https://api.hbtc.com/openapi',  # 私有API数据端点
                    'zendesk': 'https://hbtc.zendesk.com/hc/en-us',
                },
                'www': 'https://www.hbtc.com',  # 公司主页
                'referral': 'https://www.hbtc.com/register/O2S8NS',  # 邀请链接
                'doc': 'https://github.com/bhexopen/BHEX-OpenApi/tree/master/doc',  # openapi文档地址
                'fees': 'https://hbtc.zendesk.com/hc/zh-cn/articles/360009274694',  # 费率介绍
            },
            'api': {
                'public': {
                    'get': [
                        'ping',
                        'time',
                        'brokerInfo',  # 查询当前broker交易规则和symbol信息
                        'getOptions',
                    ],
                },
                'quote': {
                    'get': [
                        'depth',  # 获取深度
                        'depth/merged',
                        'trades',  # 获取当前最新成交
                        'klines',  # 获取K线数据
                        'ticker/24hr',  # 获取24小时价格变化数据
                        'ticker/price',
                        'ticker/bookTicker',
                        'contract/index',  # 获取合约标的指数价格
                        'contract/depth',  # 获取合约深度
                        'contract/depth/merged',
                        'contract/trades',  # 获取合约最近成交,
                        'contract/klines',  # 获取合约的K线数据
                        'contract/ticker/24hr',
                        'option/index',
                        'option/depth',
                        'option/depth/merged',
                        'option/trades',
                        'option/klines',
                        'option/ticker/24hr',
                    ],
                },
                'contract': {
                    'get': [
                        # public
                        'insurance',
                        'fundingRate',  # 获取资金费率信息
                        # private
                        'openOrders',  # 查询合约当前委托
                        'historyOrders',  # 查询合约历史委托
                        'getOrder',  # 查询合约订单详情
                        'myTrades',  # 查询合约历史成交
                        'positions',  # 查询合约当前持仓
                        'account',  # 查询合约账户信息
                    ],
                    'post': [
                        'order',  # 创建合约订单
                        'modifyMargin',  # 修改保证金
                    ],
                    'delete': [
                        'order/cancel',  # 取消合约订单
                        'order/batchCancel',
                    ],
                },
                'option': {
                    'get': [
                        'openOrders',
                        'positions',
                        'historyOrders',
                        # 'getOrder',
                        'myTrades',
                        'settlements',
                        'account',
                    ],
                    'post': [
                        'order',
                    ],
                    'delete': [
                        'order/cancel',
                    ],
                },
                'private': {
                    'get': [
                        'order',  # 查询订单
                        'openOrders',  # 查询当前委托
                        'historyOrders',  # 查询历史委托
                        'account',  # 获取当前账户信息
                        'myTrades',  # 查询历史成交
                        'depositOrders',
                        'withdrawalOrders',
                        'withdraw/detail',
                        'balance_flow',
                    ],
                    'post': [
                        'order',  # 创建新订单
                        'order/test',
                        'userDataStream',
                        'subAccount/query',
                        'transfer',
                        'user/transfer',
                        'withdraw',
                    ],
                    'put': [
                        'userDataStream',
                    ],
                    'delete': [
                        'order',  # 取消订单
                        'userDataStream',
                    ],
                },
            },
            'precisionMode': TICK_SIZE,
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.001,
                    'taker': 0.001,
                },
            },
            'exceptions': {
                'exact': {
                    # general server or network errors
                    '-1000': ExchangeError,  # An unknown error occured while processing the request
                    '-1001': ExchangeError,  # Internal error, unable to process your request. Please try again
                    '-1002': AuthenticationError,  # You are not authorized to execute self request. Request need API Key included in. We suggest that API Key be included in any request
                    '-1003': RateLimitExceeded,  # Too many requests, please use the websocket for live updates
                    '-1004': BadRequest,
                    '-1005': PermissionDenied,
                    '-1006': BadResponse,  # An unexpected response was received from the message bus. Execution status unknown. OPEN API server find some exception in execute request.Please report to Customer service
                    '-1007': RequestTimeout,  # Timeout waiting for response from backend server. Send status unknown, execution status unknown
                    '-1014': InvalidOrder,  # Unsupported order combination
                    '-1015': RateLimitExceeded,  # Reach the rate limit.Please slow down your request speed
                    '-1016': ExchangeNotAvailable,  # This service is no longer available
                    '-1020': NotSupported,  # This operation is not supported
                    '-1021': BadRequest,  # Timestamp for self request is outside of the recvWindow
                    '-1022': AuthenticationError,  # Signature for self request is not valid
                    # request issues
                    '-1100': BadRequest,  # Illegal characters found in a parameter
                    '-1101': BadRequest,  # Too many parameters sent for self endpoint
                    '-1102': BadRequest,  # A mandatory parameter was not sent, was empty/null, or malformed
                    '-1103': BadRequest,  # An unknown parameter was sent
                    '-1104': BadRequest,  # Not all sent parameters were read
                    '-1105': BadRequest,  # A parameter was empty
                    '-1106': BadRequest,  # A parameter was sent when not required
                    '-1111': BadRequest,  # Precision is over the maximum defined for self asset
                    '-1112': NullResponse,  # No orders on book for symbol
                    '-1114': InvalidOrder,  # TimeInForce parameter sent when not required
                    '-1115': InvalidOrder,  # Invalid timeInForce
                    '-1116': InvalidOrder,  # Invalid orderType
                    '-1117': InvalidOrder,  # Invalid side
                    '-1118': InvalidOrder,  # New client order ID was empty
                    '-1119': InvalidOrder,  # Original client order ID was empty
                    '-1120': BadRequest,  # Invalid interval
                    '-1121': BadSymbol,  # Invalid symbol
                    '-1125': AuthenticationError,  # This listenKey does not exist
                    '-1127': BadRequest,  # Lookup interval is too big
                    '-1128': BadRequest,  # Combination of optional parameters invalid
                    '-1130': BadRequest,  # Invalid data sent for a parameter
                    '-1131': InsufficientFunds,
                    '-1132': InvalidOrder,  # Order price too high
                    '-1133': InvalidOrder,  # Order price lower than the minimum,please check general broker info
                    '-1134': InvalidOrder,  # Order price decimal too long,please check general broker info
                    '-1135': InvalidOrder,  # Order quantity too large
                    '-1136': InvalidOrder,  # Order quantity lower than the minimum
                    '-1137': InvalidOrder,  # Order quantity decimal too long
                    '-1138': InvalidOrder,  # Order price exceeds permissible range
                    '-1139': InvalidOrder,  # Order has been filled
                    '-1140': InvalidOrder,  # Transaction amount lower than the minimum
                    '-1141': InvalidOrder,  # Duplicate clientOrderId
                    '-1142': InvalidOrder,  # Order has been canceled
                    '-1143': OrderNotFound,  # Cannot be found on order book
                    '-1144': InvalidOrder,  # Order has been locked
                    '-1145': InvalidOrder,  # This order type does not support cancellation
                    '-1146': RequestTimeout,  # Order creation timeout
                    '-1147': RequestTimeout,  # Order cancellation timeout
                    '-1149': InvalidOrder,  # Create order failed
                    '-1187': InvalidAddress,  # Withdrawal address not in whitelist
                    '-2010': InvalidOrder,  # NEW_ORDER_REJECTED
                    '-2011': InvalidOrder,  # CANCEL_REJECTED
                    '-2013': OrderNotFound,  # Order does not exist
                    '-2014': AuthenticationError,  # API-key format invalid
                    '-2015': AuthenticationError,  # Invalid API-key, IP, or permissions for action
                    '-2016': ExchangeError,  # No trading window could be found for the symbol. Try ticker/24hrs instead
                },
            },
            # exchange-specific options
            'options': {
                'fetchTickers': {
                    'method': 'quoteGetTicker24hr',
                },
            },
            'commonCurrencies': {
                'MIS': 'Themis Protocol',
            },
        })

    def fetch_time(self, params={}):
        response = self.publicGetTime(params)
        #
        #     {
        #         "serverTime": 1527777538000
        #     }
        #
        return self.safe_integer(response, 'serverTime')

    def parse_market(self, market, type='spot'):
        filters = self.safe_value(market, 'filters', [])
        id = self.safe_string(market, 'symbol')
        baseId = self.safe_string(market, 'baseAsset')
        quoteId = self.safe_string(market, 'quoteAsset')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        spot = True
        future = False
        option = False
        inverse = False
        if type == 'future':
            symbol = id
            spot = False
            future = True
            inverse = self.safe_value(market, 'inverse', False)
            baseId = self.safe_string(market, 'underlying')
            base = self.safe_currency_code(baseId)
        elif type == 'option':
            symbol = id
            spot = False
            option = True
        margin = self.safe_value(market, 'allowMargin', None)
        isAggregate = self.safe_value(market, 'isAggregate', None)
        active = True
        if isAggregate is True:
            active = False
        amountMin = None
        amountMax = None
        priceMin = None
        priceMax = None
        costMin = None
        pricePrecision = None
        amountPrecision = None
        for j in range(0, len(filters)):
            filter = filters[j]
            filterType = self.safe_string(filter, 'filterType')
            if filterType == 'LOT_SIZE':
                amountMin = self.safe_number(filter, 'minQty')
                amountMax = self.safe_number(filter, 'maxQty')
                amountPrecision = self.safe_number(filter, 'stepSize')
            if filterType == 'PRICE_FILTER':
                priceMin = self.safe_number(filter, 'minPrice')
                priceMax = self.safe_number(filter, 'maxPrice')
                pricePrecision = self.safe_number(filter, 'tickSize')
        if (amountMin is not None) and (priceMin is not None):
            costMin = amountMin * priceMin
        precision = {
            'price': pricePrecision,
            'amount': amountPrecision,
            'base': self.safe_number(market, 'baseAssetPrecision'),
            'quote': self.safe_number_2(market, 'quotePrecision', 'quoteAssetPrecision'),
        }
        limits = {
            'amount': {
                'min': amountMin,
                'max': amountMax,
            },
            'price': {
                'min': priceMin,
                'max': priceMax,
            },
            'cost': {
                'min': costMin,
                'max': None,
            },
        }
        return {
            'id': id,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
            'active': active,
            'type': type,
            'spot': spot,
            'future': future,
            'option': option,
            'margin': margin,
            'inverse': inverse,
            'precision': precision,
            'limits': limits,
            'info': market,
        }

    def fetch_markets(self, params={}):
        response = self.publicGetBrokerInfo(params)
        #
        #     {
        #         "timezone":"UTC",
        #         "serverTime":"1588015885118",
        #         "brokerFilters":[],
        #         "symbols":[
        #             {
        #                 "filters":[
        #                     {"minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"},
        #                     {"minQty":"0.0005","maxQty":"100000.00000000","stepSize":"0.000001","filterType":"LOT_SIZE"},
        #                     {"minNotional":"0.01","filterType":"MIN_NOTIONAL"}
        #                 ],
        #                 "exchangeId":"301",
        #                 "symbol":"BTCUSDT",
        #                 "symbolName":"BTCUSDT",
        #                 "status":"TRADING",
        #                 "baseAsset":"BTC",
        #                 "baseAssetName":"BTC",
        #                 "baseAssetPrecision":"0.000001",
        #                 "quoteAsset":"USDT",
        #                 "quoteAssetName":"USDT",
        #                 "quotePrecision":"0.01",
        #                 "icebergAllowed":false,
        #                 "isAggregate":false,
        #                 "allowMargin":true
        #            },
        #         ],
        #         "options":[
        #             {
        #                 "filters":[
        #                     {"minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"},
        #                     {"minQty":"0.01","maxQty":"100000.00000000","stepSize":"0.001","filterType":"LOT_SIZE"},
        #                     {"minNotional":"1","filterType":"MIN_NOTIONAL"}
        #                 ],
        #                 "exchangeId":"301",
        #                 "symbol":"BTC0501CS8500",
        #                 "symbolName":"BTC0501CS8500",
        #                 "status":"TRADING",
        #                 "baseAsset":"BTC0501CS8500",
        #                 "baseAssetName":"BTC0306CS3800",
        #                 "baseAssetPrecision":"0.001",
        #                 "quoteAsset":"BUSDT",
        #                 "quoteAssetName":"BUSDT",
        #                 "quotePrecision":"0.01",
        #                 "icebergAllowed":false
        #                 "isAggregate":false,
        #                 "allowMargin":false
        #             },
        #         ],
        #         "contracts":[
        #             {
        #                 "filters":[
        #                     {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"},
        #                     {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"},
        #                     {"minNotional":"0.000001","filterType":"MIN_NOTIONAL"}
        #                 ],
        #                 "exchangeId":"301",
        #                 "symbol":"BTC-PERP-REV",
        #                 "symbolName":"BTC-PERP-REV",
        #                 "status":"TRADING",
        #                 "baseAsset":"BTC-PERP-REV",
        #                 "baseAssetPrecision":"1",
        #                 "quoteAsset":"USDT",
        #                 "quoteAssetPrecision":"0.1",
        #                 "icebergAllowed":false,
        #                 "inverse":true,
        #                 "index":"BTCUSDT",
        #                 "marginToken":"TBTC",
        #                 "marginPrecision":"0.00000001",
        #                 "contractMultiplier":"1.0",
        #                 "underlying":"TBTC",
        #                 "riskLimits":[
        #                     {"riskLimitId":"200000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"},
        #                     {"riskLimitId":"200000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"},
        #                     {"riskLimitId":"200000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"},
        #                     {"riskLimitId":"200000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"}
        #                 ]
        #             },
        #             {
        #                 "filters":[
        #                     {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"},
        #                     {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"},
        #                     {"minNotional":"0.000001","filterType":"MIN_NOTIONAL"}
        #                 ],
        #                 "exchangeId":"301",
        #                 "symbol":"BTC-SWAP",
        #                 "symbolName":"BTC-SWAP",
        #                 "status":"TRADING",
        #                 "baseAsset":"BTC-SWAP",
        #                 "baseAssetPrecision":"1",
        #                 "quoteAsset":"USDT",
        #                 "quoteAssetPrecision":"0.1",
        #                 "icebergAllowed":false,
        #                 "inverse":true,
        #                 "index":"BTCUSDT",
        #                 "marginToken":"BTC",
        #                 "marginPrecision":"0.00000001",
        #                 "contractMultiplier":"1.0",
        #                 "underlying":"BTC",
        #                 "riskLimits":[
        #                     {"riskLimitId":"500000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"},
        #                     {"riskLimitId":"500000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"},
        #                     {"riskLimitId":"500000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"},
        #                     {"riskLimitId":"500000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"}
        #                 ]
        #             },
        #             {
        #                 "filters":[
        #                     {"minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"},
        #                     {"minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"},
        #                     {"minNotional":"0.000000001","filterType":"MIN_NOTIONAL"}
        #                 ],
        #                 "exchangeId":"301",
        #                 "symbol":"BTC-PERP-BUSDT",
        #                 "symbolName":"BTC-PERP-BUSDT",
        #                 "status":"TRADING",
        #                 "baseAsset":"BTC-PERP-BUSDT",
        #                 "baseAssetPrecision":"1",
        #                 "quoteAsset":"BUSDT",
        #                 "quoteAssetPrecision":"0.1",
        #                 "icebergAllowed":false,
        #                 "inverse":false,
        #                 "index":"BTCUSDT",
        #                 "marginToken":"BUSDT",
        #                 "marginPrecision":"0.0001",
        #                 "contractMultiplier":"0.0001",
        #                 "underlying":"TBTC",
        #                 "riskLimits":[
        #                     {"riskLimitId":"600000132","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"},
        #                     {"riskLimitId":"600000133","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"},
        #                     {"riskLimitId":"600000134","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"},
        #                     {"riskLimitId":"600000135","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02"}
        #                 ]
        #             },
        #         ]
        #     }
        #
        result = []
        symbols = self.safe_value(response, 'symbols', [])
        for i in range(0, len(symbols)):
            market = self.parse_market(symbols[i], 'spot')
            result.append(market)
        options = self.safe_value(response, 'options', [])
        for i in range(0, len(options)):
            market = self.parse_market(options[i], 'option')
            result.append(market)
        contracts = self.safe_value(response, 'contracts', [])
        for i in range(0, len(contracts)):
            market = self.parse_market(contracts[i], 'future')
            result.append(market)
        return result

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 40, max 40
        response = self.quoteGetDepth(self.extend(request, params))
        #
        #     {
        #         "time":1588068913453,
        #         "bids":[
        #             ["0.025278","0.0202"],
        #             ["0.025277","16.1132"],
        #             ["0.025276","7.9056"],
        #         ]
        #         "asks":[
        #             ["0.025302","5.9999"],
        #             ["0.025303","34.9151"],
        #             ["0.025304","92.391"],
        #         ]
        #     }
        #
        timestamp = self.safe_integer(response, 'time')
        return self.parse_order_book(response, symbol, timestamp)

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.quoteGetTicker24hr(self.extend(request, params))
        #
        #     {
        #         "time":1588069860794,
        #         "symbol":"BNB0501PS16",
        #         "bestBidPrice":"0.2129",
        #         "bestAskPrice":"0.3163",
        #         "volume":"33547",
        #         "quoteVolume":"10801.987",
        #         "lastPrice":"0.2625",
        #         "highPrice":"0.3918",
        #         "lowPrice":"0.2625",
        #         "openPrice":"0.362",
        #     }
        #
        return self.parse_ticker(response, market)

    def fetch_bid_ask(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.quoteGetTickerBookTicker(self.extend(request, params))
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "bidPrice": "4.00000000",
        #         "bidQty": "431.00000000",
        #         "askPrice": "4.00000200",
        #         "askQty": "9.00000000"
        #     }
        #
        return self.parse_ticker(response, market)

    def fetch_bids_asks(self, symbols=None, params={}):
        self.load_markets()
        response = self.quoteGetTickerBookTicker(params)
        #
        #     [
        #         {
        #             "symbol": "LTCBTC",
        #             "bidPrice": "4.00000000",
        #             "bidQty": "431.00000000",
        #             "askPrice": "4.00000200",
        #             "askQty": "9.00000000"
        #         },
        #         {
        #             "symbol": "ETHBTC",
        #             "bidPrice": "0.07946700",
        #             "bidQty": "9.00000000",
        #             "askPrice": "100000.00000000",
        #             "askQty": "1000.00000000"
        #         },
        #     ]
        #
        return self.parse_tickers(response, symbols)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        options = self.safe_value(self.options, 'fetchTickers', {})
        defaultMethod = self.safe_string(options, 'method', 'quoteGetTicker24hr')
        defaultType = self.safe_string(options, 'type', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        method = defaultMethod
        if type == 'future':
            method = 'quoteGetContractTicker24hr'
        elif type == 'option':
            method = 'quoteGetOptionTicker24hr'
        response = getattr(self, method)(query)
        #
        #     [
        #         {
        #             "time": 1538725500422,
        #             "symbol": "ETHBTC",
        #             "lastPrice": "4.00000200",
        #             "openPrice": "99.00000000",
        #             "highPrice": "100.00000000",
        #             "lowPrice": "0.10000000",
        #             "volume": "8913.30000000"
        #         },
        #     ]
        #
        return self.parse_tickers(response, symbols)

    def fetch_balance(self, params={}):
        self.load_markets()
        options = self.safe_value(self.options, 'fetchBalance', {})
        defaultType = self.safe_string(options, 'type', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        method = 'privateGetAccount'
        if type == 'future':
            method = 'contractGetAccount'
        elif type == 'option':
            method = 'optionGetAccount'
        response = getattr(self, method)(query)
        #
        # spot
        #
        #     {
        #         'balances': [
        #             {
        #                 'asset': 'ALGO',
        #                 'free': '0',
        #                 'locked': '0'
        #             },
        #             {
        #                 'asset': 'BHT',
        #                 'free': '0',
        #                 'locked': '0'
        #             }
        #         ]
        #     }
        #
        # contract
        #
        #     {
        #         "BUSDT":{
        #             "total":"1000",
        #             "availableMargin":"1000",
        #             "positionMargin":"0",
        #             "orderMargin":"0",
        #             "tokenId":"BUSDT"
        #         },
        #         "TBTC":{
        #             "total":"0.5",
        #             "availableMargin":"0.5",
        #             "positionMargin":"0",
        #             "orderMargin":"0",
        #             "tokenId":"TBTC"
        #         }
        #     }
        #
        # option
        #
        #     {
        #         "optionAsset":"",
        #         "balances":[
        #             {
        #                 "tokenName":"USDT",
        #                 "free":"0.0",
        #                 "locked":"0.0",
        #                 "margin":"0.0"
        #             },
        #             {
        #                 "tokenName":"BUSDT",
        #                 "free":"0.0",
        #                 "locked":"0.0",
        #                 "margin":"0.0"
        #             }
        #         ]
        #     }
        #
        balances = self.safe_value(response, 'balances')
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        if balances is not None:
            for i in range(0, len(balances)):
                balance = balances[i]
                currencyId = self.safe_string_2(balance, 'asset', 'tokenName')
                code = self.safe_currency_code(currencyId)
                account = self.account()
                account['free'] = self.safe_string(balance, 'free')
                account['used'] = self.safe_string(balance, 'locked')
                result[code] = account
        else:
            currencyIds = list(response.keys())
            for i in range(0, len(currencyIds)):
                currencyId = currencyIds[i]
                code = self.safe_currency_code(currencyId)
                balance = response[currencyId]
                account = self.account()
                account['free'] = self.safe_string(balance, 'availableMargin')
                account['total'] = self.safe_string(balance, 'total')
                result[code] = account
        return self.parse_balance(result, False)

    def fetch_trades(self, symbol, since=None, limit=50, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 500, max 1000
        response = self.quoteGetTrades(self.extend(request, params))
        #
        #     [
        #         {"price":"0.025344","time":1588084082060,"qty":"1","isBuyerMaker":false},
        #         {"price":"0.02535","time":1588084086021,"qty":"0.553","isBuyerMaker":true},
        #         {"price":"0.025348","time":1588084097037,"qty":"1","isBuyerMaker":false},
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1587906000000,  # open time
        #         "0.1761",  # open
        #         "0.1761",  # high
        #         "0.1761",  # low
        #         "0.1761",  # close
        #         "0",  # base volume
        #         0,  # close time
        #         "0",  # quote volume
        #         0,  # number of trades
        #         "0",  # taker buy base asset volume
        #         "0"  # taker buy quote asset volume
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 5),
        ]

    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 is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit  # default 500, max 500
        response = self.quoteGetKlines(self.extend(request, params))
        #
        #     [
        #         [1587906000000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"],
        #         [1587906180000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"],
        #         [1587906360000,"0.1761","0.1848","0.1761","0.1848","53",0,"9.7944",1,"0","0"],
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {
            # if only fromId is set，it will get orders < that fromId in descending order
            # if only toId is set, it will get orders > that toId in ascending order
            # if fromId is set and toId is set, it will get orders < that fromId and > that toId in descending order
            # if fromId is not set and toId it not set, most recent order are returned in descending order
            # 'fromId': '43287482374',
            # 'toId': '43287482374',
            # 'endTime': self.milliseconds(),  # optional, spot only
        }
        defaultType = self.safe_string(self.options, 'type', 'spot')
        options = self.safe_value(self.options, 'fetchMyTrades', {})
        fetchMyTradesType = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', fetchMyTradesType)
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
            type = market['type']
        query = self.omit(params, 'type')
        if limit is not None:
            # spot default 500, max 1000
            # futures and options default 20, max 1000
            request['limit'] = limit
        method = 'privateGetMyTrades'
        if type == 'future':
            method = 'contractGetMyTrades'
        else:
            if type == 'option':
                method = 'optionGetMyTrades'
            else:
                if symbol is None:
                    raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a `symbol` argument for ' + type + ' markets')
                market = self.market(symbol)
                request['symbol'] = market['id']
                # spot only?
                if since is not None:
                    request['startTime'] = since
        if since is not None:
            request['startTime'] = since
        response = getattr(self, method)(self.extend(request, query))
        #
        # spot
        #
        #     [
        #         {
        #             "id":"616384027512920576",
        #             "symbol":"TBTCBUSDT",
        #             "orderId":"616384027202542080",
        #             "matchOrderId":"605124954767266560",
        #             "price":"6826.06",
        #             "qty":"0.1",
        #             "commission":"0.682606",
        #             "commissionAsset":"BUSDT",
        #             "time":"1588214701982",
        #             "isBuyer":false,
        #             "isMaker":false,
        #             "fee":{
        #                 "feeTokenId":"BUSDT",
        #                 "feeTokenName":"BUSDT",
        #                 "fee":"0.682606"
        #             }
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        orderSide = side.upper()
        orderType = type.upper()
        request = {
            'symbol': market['id'],
            # BUY or SELL for spot and options
            'side': orderSide,
            # GTC, FOK, IOC for spot and options
            # GTC, FOK, IOC, LIMIT_MAKER for futures
            # 'timeInForce': 'GTC',
        }
        query = params
        method = 'privatePostOrder'
        if market['type'] == 'future':
            if (orderSide != 'BUY_OPEN') and (orderSide != 'SELL_OPEN') and (orderSide != 'BUY_CLOSE') and (orderSide != 'SELL_CLOSE'):
                raise NotSupported(self.id + ' createOrder() does not support order side ' + side + ' for ' + market['type'] + ' markets, only BUY_OPEN, SELL_OPEN, BUY_CLOSE and SELL_CLOSE are supported')
            if (orderType != 'LIMIT') and (orderType != 'STOP'):
                raise NotSupported(self.id + ' createOrder() does not support order type ' + type + ' for ' + market['type'] + ' markets, only LIMIT and STOP are supported')
            clientOrderId = self.safe_value(params, 'clientOrderId')
            if clientOrderId is None:
                raise ArgumentsRequired(self.id + ' createOrder() requires a clientOrderId parameter for ' + market['type'] + ' markets, supply clientOrderId in the params argument')
            leverage = self.safe_value(params, 'leverage')
            if leverage is None and (orderSide == 'BUY_OPEN' or orderSide == 'SELL_OPEN'):
                raise NotSupported(self.id + ' createOrder() requires a leverage parameter for ' + market['type'] + ' markets if orderSide is BUY_OPEN or SELL_OPEN')
            method = 'contractPostOrder'
            priceType = self.safe_string(params, 'priceType')
            if priceType is None:
                request['price'] = self.price_to_precision(symbol, price)
            else:
                request['priceType'] = priceType
                if priceType == 'INPUT':
                    request['price'] = self.price_to_precision(symbol, price)
            request['orderType'] = type.upper()  # LIMIT, STOP
            request['quantity'] = self.amount_to_precision(symbol, amount)
            # request['leverage'] = 1  # not required for closing orders
            request['leverage'] = leverage
            request['clientOrderId'] = clientOrderId
            # optional
            # request['priceType'] = 'INPUT',  # INPUT, OPPONENT, QUEUE, OVER, MARKET
            # request['triggerPrice'] = 123.45
        else:
            if market['type'] == 'option':
                method = 'optionPostOrder'
            newClientOrderId = self.safe_value_2(params, 'clientOrderId', 'newClientOrderId')
            if newClientOrderId is not None:
                request['newClientOrderId'] = newClientOrderId
            request['type'] = orderType
            if type == 'limit':
                request['price'] = self.price_to_precision(symbol, price)
                request['quantity'] = self.amount_to_precision(symbol, amount)
            elif type == 'market':
                # for market buy it requires the amount of quote currency to spend
                if side == 'buy':
                    createMarketBuyOrderRequiresPrice = self.safe_value(self.options, 'createMarketBuyOrderRequiresPrice', True)
                    if createMarketBuyOrderRequiresPrice:
                        if price is not None:
                            amount = amount * price
                        else:
                            raise InvalidOrder(self.id + " createOrder() requires the price argument with market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = False and supply the total cost value in the 'amount' argument(the exchange-specific behaviour)")
                    precision = market['precision']['price']
                    request['quantity'] = self.decimal_to_precision(amount, TRUNCATE, precision, self.precisionMode)
                else:
                    request['quantity'] = self.amount_to_precision(symbol, amount)
        query = self.omit(query, ['clientOrderId', 'newClientOrderId'])
        response = getattr(self, method)(self.extend(request, query))
        #
        # spot
        #
        #     {
        #         "symbol":"TBTCBUSDT",
        #         "orderId":"616376654496877056",
        #         "clientOrderId":"158821382304516955",
        #         "transactTime":"1588213823080",
        #         "price":"0",
        #         "origQty":"1000",
        #         "executedQty":"0",
        #         "status":"NEW",
        #         "timeInForce":"GTC",
        #         "type":"MARKET",
        #         "side":"BUY"
        #     }
        #
        # contract
        #
        #     {
        #         'time': '1570759718825',
        #         'updateTime': '0',
        #         'orderId': '469961015902208000',
        #         'clientOrderId': '6423344174',
        #         'symbol': 'BTC-PERP-REV',
        #         'price': '8200',
        #         'leverage': '12.08',
        #         'origQty': '5',
        #         'executedQty': '0',
        #         'avgPrice': '0',
        #         'marginLocked': '0.00005047',
        #         'orderType': 'LIMIT',
        #         'side': 'BUY_OPEN',
        #         'fees': [],
        #         'timeInForce': 'GTC',
        #         'status': 'NEW',
        #         'priceType': 'INPUT'
        #     }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        clientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
        request = {}
        defaultType = self.safe_string(self.options, 'type', 'spot')
        options = self.safe_value(self.options, 'cancelOrder', {})
        cancelOrderType = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', cancelOrderType)
        query = self.omit(params, 'type')
        if clientOrderId is not None:
            request['origClientOrderId'] = clientOrderId
            query = self.omit(query, ['origClientOrderId', 'clientOrderId'])
        else:
            request['orderId'] = id
        method = 'privateDeleteOrder'
        orderType = self.safe_string(query, 'orderType')
        if orderType is not None:
            type = 'future'
        if type == 'future':
            method = 'contractDeleteOrderCancel'
            if orderType is None:
                raise ArgumentsRequired(self.id + " cancelOrder() requires an orderType parameter, pass the {'orderType': 'LIMIT'} or {'orderType': 'STOP'} in params argument")
            request['orderType'] = orderType
        else:
            if type == 'option':
                method = 'optionDeleteOrderCancel'
        response = getattr(self, method)(self.extend(request, query))
        #
        # spot
        #
        #     {
        #         'exchangeId': '301',
        #         'symbol': 'BHTUSDT',
        #         'clientOrderId': '0',
        #         'orderId': '499890200602846976',
        #         'status': 'CANCELED'
        #     }
        #
        # futures
        #
        #     {
        #         "time":"1588353669383",
        #         "updateTime":"0",
        #         "orderId":"617549770304599296",
        #         "clientOrderId":"test-001",
        #         "symbol":"BTC-PERP-REV",
        #         "price":"10000",
        #         "leverage":"1",
        #         "origQty":"100",
        #         "executedQty":"0",
        #         "avgPrice":"0",
        #         "marginLocked":"0",
        #         "orderType":"LIMIT",
        #         "side":"SELL_OPEN",
        #         "fees":[],
        #         "timeInForce":"GTC",
        #         "status":"CANCELED",
        #         "priceType":"INPUT",
        #     }
        #
        return self.parse_order(response)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {
            # if orderId is set, it will get orders < that orderId otherwise most recent orders are returned
            # 'orderId': '43287482374',
        }
        defaultType = self.safe_string(self.options, 'type', 'spot')
        options = self.safe_value(self.options, 'fetchOpenOrders', {})
        fetchOpenOrdersType = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', fetchOpenOrdersType)
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
            type = market['type']
        query = self.omit(params, 'type')
        if limit is not None:
            request['limit'] = limit  # default 500, max 1000
        method = 'privateGetOpenOrders'
        if type == 'future':
            method = 'contractGetOpenOrders'
        elif type == 'option':
            method = 'optionGetOpenOrders'
        response = getattr(self, method)(self.extend(request, query))
        #
        # spot
        #
        #     [
        #         {
        #             'orderId': '499902955766523648',
        #             'clientOrderId': '157432907618453',
        #             'exchangeId': '301',
        #             'symbol': 'BHTUSDT',
        #             'price': '0.01',
        #             'origQty': '50',
        #             'executedQty': '0',
        #             'cummulativeQuoteQty': '0',
        #             'avgPrice': '0',
        #             'status': 'NEW',
        #             'timeInForce': 'GTC',
        #             'type': 'LIMIT',
        #             'side': 'BUY',
        #             'stopPrice': '0.0',
        #             'icebergQty': '0.0',
        #             'time': '1574329076202',
        #             'updateTime': '0',
        #             'isWorking': True
        #         }
        #     ]
        #
        # futures
        #
        #     [
        #         {
        #             "time":"1588353669383",
        #             "updateTime":"0",
        #             "orderId":"617549770304599296",
        #             "clientOrderId":"test-001",
        #             "symbol":"BTC-PERP-REV",
        #             "price":"10000",
        #             "leverage":"1",
        #             "origQty":"100",
        #             "executedQty":"0",
        #             "avgPrice":"0",
        #             "marginLocked":"0.01",
        #             "orderType":"LIMIT",
        #             "side":"SELL_OPEN",
        #             "fees":[],
        #             "timeInForce":"GTC",
        #             "status":"NEW",
        #             "priceType":"INPUT"
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {
            # if orderId is set, it will get orders < that orderId otherwise most recent orders are returned
            # 'orderId': '43287482374',
            # 'endTime': self.milliseconds(),  # optional
        }
        defaultType = self.safe_string(self.options, 'type', 'spot')
        options = self.safe_value(self.options, 'fetchClosedOrders', {})
        fetchClosedOrdersType = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', fetchClosedOrdersType)
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
            type = market['type']
        query = self.omit(params, 'type')
        if limit is not None:
            request['limit'] = limit  # default 500, max 1000
        if since is not None:
            request['startTime'] = since
        method = 'privateGetHistoryOrders'
        if type == 'future':
            method = 'contractGetHistoryOrders'
        elif type == 'option':
            method = 'optionGetHistoryOrders'
        response = getattr(self, method)(self.extend(request, query))
        #
        # spot
        #
        #     [
        #         {
        #             "orderId":"616384027202542080",
        #             "clientOrderId":"158821470194414688",
        #             "exchangeId":"301",
        #             "symbol":"TBTCBUSDT",
        #             "price":"0",
        #             "origQty":"0.1",
        #             "executedQty":"0.1",
        #             "cummulativeQuoteQty":"682.606",
        #             "avgPrice":"6826.06",
        #             "status":"FILLED",
        #             "timeInForce":"GTC",
        #             "type":"MARKET",
        #             "side":"SELL",
        #             "stopPrice":"0.0",
        #             "icebergQty":"0.0",
        #             "time":"1588214701974",
        #             "updateTime":"0",
        #             "isWorking":true
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        clientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
        request = {}
        defaultType = self.safe_string(self.options, 'type', 'spot')
        options = self.safe_value(self.options, 'fetchOrder', {})
        fetchOrderType = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', fetchOrderType)
        query = self.omit(params, 'type')
        if clientOrderId is not None:
            request['origClientOrderId'] = clientOrderId
            query = self.omit(query, ['origClientOrderId', 'clientOrderId'])
        else:
            request['orderId'] = id
        method = 'privateGetOrder'
        if type == 'future':
            method = 'contractGetGetOrder'
        elif type == 'option':
            method = 'optionGetGetOrder'
        response = getattr(self, method)(self.extend(request, query))
        return self.parse_order(response)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        currency = None
        request = {
            # 'fromId': 'string',  # if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned
        }
        if code is not None:
            currency = self.currency(code)
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        response = self.privateGetDepositOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             'time': '1565769575929',
        #             'orderId': '428100569859739648',
        #             'token': 'USDT',
        #             'address': '',
        #             'addressTag': '',
        #             'fromAddress': '',
        #             'fromAddressTag': '',
        #             'quantity': '1100',
        #         },
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        currency = None
        request = {
            # 'fromId': 'string',  # if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned
        }
        if code is not None:
            currency = self.currency(code)
            request['token'] = currency['id']
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit  # default 500, max 1000
        response = self.privateGetWithdrawalOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "time":"1536232111669",
        #             "orderId":"90161227158286336",
        #             "accountId":"517256161325920",
        #             "tokenId":"BHC",
        #             "tokenName":"BHC",
        #             "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
        #             "addressExt":"address tag",
        #             "quantity":"14",  # Withdrawal qty
        #             "arriveQuantity":"14",  # Arrived qty
        #             "statusCode":"PROCESSING_STATUS",
        #             "status":3,
        #             "txid":"",
        #             "txidUrl":"",
        #             "walletHandleTime":"1536232111669",
        #             "feeTokenId":"BHC",
        #             "feeTokenName":"BHC",
        #             "fee":"0.1",
        #             "requiredConfirmNum":0,  # Required confirmations
        #             "confirmNum":0,  # Confirmations
        #             "kernelId":"",  # BEAM and GRIN only
        #             "isInternalTransfer": False  # True if self transfer is internal
        #         }
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit)

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        clientOrderId = self.safe_string(params, 'clientOrderId', self.uuid())
        request = {
            'clientOrderId': clientOrderId,
            'tokenId': currency['id'],
            'address': address,  # the withdrawal address must be in current tag list in your PC/APP client
            'withdrawQuantity': amount,
            # 'chainType': 'OMNI',  # OMNI, ERC20, TRC20
        }
        if tag is not None:
            request['addressExt'] = tag
        response = self.privatePostWithdraw(self.extend(request, params))
        #
        #     {
        #         "status": 0,
        #         "success": True,
        #         "needBrokerAudit": False,  # Whether self request needs broker auit
        #         "orderId": "423885103582776064"  # Id for successful withdrawal
        #     }
        #
        return {
            'info': response,
            'id': self.safe_string(response, 'orderId'),
        }

    def fetch_accounts(self, params={}):
        response = self.privatePostSubAccountQuery(params)
        #
        #     [
        #         {
        #             "accountId": "122216245228131",
        #             "accountName": "createSubAccountByCurl",  # sub-account name
        #             "accountType": 1,  # 1 token trading, 2 options, 3 futures
        #             "accountIndex": 1,  # 0 main account, 1 sub-account
        #         },
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            account = response[i]
            accountId = self.safe_string(account, 'accountId')
            accountType = self.safe_string(account, 'accountType')
            type = accountType
            if accountType == '1':
                type = 'spot'
            elif accountType == '2':
                type = 'option'
            elif accountType == '3':
                type = 'future'
            result.append({
                'id': accountId,
                'type': type,
                'currency': None,
                'info': account,
            })
        return result

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'accountType': 1,  # spot 1, options 2, futures 3
            'accountIndex': 0,  # main 0, sub-account 1
            'fromFlowId': '',  # flowId to start from
            'endFlowId': '',  # flowId to end with
            'endTime': 1588450533040,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['tokenId'] = currency['id']
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit  # default 500, max 500
        response = self.privateGetBalanceFlow(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": "539870570957903104",
        #             "accountId": "122216245228131",
        #             "tokenId": "BTC",
        #             "tokenName": "BTC",
        #             "flowTypeValue": 51,
        #             "flowType": "USER_ACCOUNT_TRANSFER",
        #             "flowName": "Transfer",
        #             "change": "-12.5",
        #             "total": "379.624059937852365",  # after change
        #             "created": "1579093587214"
        #         },
        #         {
        #             "id": "536072393645448960",
        #             "accountId": "122216245228131",
        #             "tokenId": "USDT",
        #             "tokenName": "USDT",
        #             "flowTypeValue": 7,
        #             "flowType": "AIRDROP",
        #             "flowName": "Airdrop",
        #             "change": "-2000",
        #             "total": "918662.0917630848",
        #             "created": "1578640809195"
        #         }
        #     ]
        #
        return self.parse_ledger(response, currency, since, limit)

    def parse_ledger_entry(self, item, currency=None):
        #
        #     {
        #         "id": "539870570957903104",
        #         "accountId": "122216245228131",
        #         "tokenId": "BTC",
        #         "tokenName": "BTC",
        #         "flowTypeValue": 51,
        #         "flowType": "USER_ACCOUNT_TRANSFER",
        #         "flowName": "Transfer",
        #         "change": "-12.5",
        #         "total": "379.624059937852365",  # after change
        #         "created": "1579093587214"
        #     }
        #
        #     {
        #         "id": "536072393645448960",
        #         "accountId": "122216245228131",
        #         "tokenId": "USDT",
        #         "tokenName": "USDT",
        #         "flowTypeValue": 7,
        #         "flowType": "AIRDROP",
        #         "flowName": "Airdrop",
        #         "change": "-2000",
        #         "total": "918662.0917630848",
        #         "created": "1578640809195"
        #     }
        #
        currencyId = self.safe_string(item, 'tokenId')
        code = self.safe_currency_code(currencyId, currency)
        amount = self.safe_number(item, 'change')
        after = self.safe_number(item, 'total')
        direction = 'out' if (amount < 0) else 'in'
        before = None
        if after is not None and amount is not None:
            difference = amount if (direction == 'out') else -amount
            before = self.sum(after, difference)
        timestamp = self.safe_integer(item, 'created')
        type = self.parse_ledger_entry_type(self.safe_string(item, 'flowType'))
        id = self.safe_string(item, 'id')
        account = self.safe_string(item, 'accountId')
        return {
            'id': id,
            'currency': code,
            'account': account,
            'referenceAccount': None,
            'referenceId': None,
            'status': None,
            'amount': amount,
            'before': before,
            'after': after,
            'fee': None,
            'direction': direction,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'type': type,
            'info': item,
        }

    def parse_ledger_entry_type(self, type):
        types = {
            'TRADE': 'trade',
            'FEE': 'fee',
            'TRANSFER': 'transfer',
            'DEPOSIT': 'transaction',
            'MAKER_REWARD': 'rebate',
            'PNL': 'pnl',
            'SETTLEMENT': 'settlement',
            'LIQUIDATION': 'liquidation',
            'FUNDING_SETTLEMENT': 'settlement',
            'USER_ACCOUNT_TRANSFER': 'transfer',
            'OTC_BUY_COIN': 'trade',
            'OTC_SELL_COIN': 'trade',
            'OTC_FEE': 'fee',
            'OTC_TRADE': 'trade',
            'ACTIVITY_AWARD': 'referral',
            'INVITATION_REFERRAL_BONUS': 'referral',
            'REGISTER_BONUS': 'referral',
            'AIRDROP': 'airdrop',
            'MINE_REWARD': 'reward',
        }
        return self.safe_string(types, type, type)

    def parse_transaction_status(self, status):
        statuses = {
            'BROKER_AUDITING_STATUS': 'pending',
            'BROKER_REJECT_STATUS': 'failed',
            'AUDITING_STATUS': 'pending',
            'AUDIT_REJECT_STATUS': 'failed',
            'PROCESSING_STATUS': 'pending',
            'WITHDRAWAL_SUCCESS_STATUS': 'ok',
            'WITHDRAWAL_FAILURE_STATUS': 'failed',
            'BLOCK_MINING_STATUS': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #         'time': '1565769575929',
        #         'orderId': '428100569859739648',
        #         'token': 'USDT',
        #         'address': '',
        #         'addressTag': '',
        #         'fromAddress': '',
        #         'fromAddressTag': '',
        #         'quantity': '1100',
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "time":"1536232111669",
        #         "orderId":"90161227158286336",
        #         "accountId":"517256161325920",
        #         "tokenId":"BHC",
        #         "tokenName":"BHC",
        #         "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
        #         "addressExt":"address tag",
        #         "quantity":"14",  # Withdrawal qty
        #         "arriveQuantity":"14",  # Arrived qty
        #         "statusCode":"PROCESSING_STATUS",
        #         "status":3,
        #         "txid":"",
        #         "txidUrl":"",
        #         "walletHandleTime":"1536232111669",
        #         "feeTokenId":"BHC",
        #         "feeTokenName":"BHC",
        #         "fee":"0.1",
        #         "requiredConfirmNum":0,  # Required confirmations
        #         "confirmNum":0,  # Confirmations
        #         "kernelId":"",  # BEAM and GRIN only
        #         "isInternalTransfer": False  # True if self transfer is internal
        #     }
        #
        id = self.safe_string(transaction, 'orderId')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string_2(transaction, 'addressExt', 'addressTag')
        if tag is not None:
            if len(tag) < 1:
                tag = None
        addressFrom = self.safe_string(transaction, 'fromAddress')
        tagFrom = self.safe_string(transaction, 'fromAddressTag')
        if tagFrom is not None:
            if len(tagFrom) < 1:
                tagFrom = None
        currencyId = self.safe_string(transaction, 'tokenId')
        code = self.safe_currency_code(currencyId, currency)
        timestamp = self.safe_integer(transaction, 'time')
        txid = self.safe_string(transaction, 'txid')
        if txid == '':
            txid = None
        type = None
        status = self.parse_transaction_status(self.safe_string(transaction, 'statusCode'))
        if status is None:
            type = 'deposit'
            status = 'ok'
        else:
            type = 'withdrawal'
        amount = self.safe_number(transaction, 'quantity')
        feeCost = self.safe_number(transaction, 'fee')
        fee = None
        if feeCost is not None:
            feeCurrencyId = self.safe_string(transaction, 'feeTokenId')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'currency': feeCurrencyCode,
                'cost': feeCost,
            }
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'addressFrom': addressFrom,
            'address': address,
            'addressTo': address,
            'tagFrom': tagFrom,
            'tag': tag,
            'tagTo': tag,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'fee': fee,
        }

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker, fetchTickers
        #
        #     {
        #         "time":1588069860794,
        #         "symbol":"BNB0501PS16",
        #         "bestBidPrice":"0.2129",
        #         "bestAskPrice":"0.3163",
        #         "volume":"33547",
        #         "quoteVolume":"10801.987",
        #         "lastPrice":"0.2625",
        #         "highPrice":"0.3918",
        #         "lowPrice":"0.2625",
        #         "openPrice":"0.362",
        #     }
        #
        # fetchBidAsk, fetchBidAsks
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "bidPrice": "4.00000000",
        #         "bidQty": "431.00000000",
        #         "askPrice": "4.00000200",
        #         "askQty": "9.00000000"
        #     }
        #
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_integer(ticker, 'time')
        open = self.safe_number(ticker, 'openPrice')
        close = self.safe_number(ticker, 'lastPrice')
        change = None
        percentage = None
        average = None
        if (open is not None) and (close is not None):
            change = close - open
            average = self.sum(open, close) / 2
            if (close is not None) and (close > 0):
                percentage = (change / open) * 100
        quoteVolume = self.safe_number(ticker, 'quoteVolume')
        baseVolume = self.safe_number(ticker, 'volume')
        vwap = self.vwap(baseVolume, quoteVolume)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'highPrice'),
            'low': self.safe_number(ticker, 'lowPrice'),
            'bid': self.safe_number_2(ticker, 'bestBidPrice', 'bidPrice'),
            'bidVolume': self.safe_number(ticker, 'bidQty'),
            'ask': self.safe_number_2(ticker, 'bestAskPrice', 'askPrice'),
            'askVolume': self.safe_number(ticker, 'askQty'),
            'vwap': vwap,
            'open': open,
            'close': close,
            'last': close,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    def parse_trade(self, trade, market):
        #
        # fetchTrades(public)
        #
        #     {
        #         "price":"0.025344",
        #         "time":1588084082060,
        #         "qty":"1",
        #         "isBuyerMaker":false
        #     }
        #
        # fetchMyTrades(private)
        #
        # spot
        #
        #     {
        #         "id":"616384027512920576",
        #         "symbol":"TBTCBUSDT",
        #         "orderId":"616384027202542080",
        #         "matchOrderId":"605124954767266560",
        #         "price":"6826.06",
        #         "qty":"0.1",
        #         "commission":"0.682606",
        #         "commissionAsset":"BUSDT",
        #         "time":"1588214701982",
        #         "isBuyer":false,
        #         "isMaker":false,
        #         "fee":{
        #             "feeTokenId":"BUSDT",
        #             "feeTokenName":"BUSDT",
        #             "fee":"0.682606"
        #         }
        #     }
        #
        id = self.safe_string(trade, 'id')
        timestamp = self.safe_number(trade, 'time')
        type = None
        orderId = self.safe_string(trade, 'orderId')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'qty')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.parse_number(Precise.string_mul(priceString, amountString))
        side = None
        takerOrMaker = None
        if 'isBuyerMaker' in trade:
            side = 'sell' if trade['isBuyerMaker'] else 'buy'
        else:
            isMaker = self.safe_value(trade, 'isMaker')
            if isMaker is not None:
                takerOrMaker = 'maker' if isMaker else 'taker'
            isBuyer = self.safe_value(trade, 'isBuyer')
            side = 'buy' if isBuyer else 'sell'
        fee = None
        feeCost = self.safe_number(trade, 'commission')
        if feeCost is not None:
            feeCurrencyId = self.safe_string(trade, 'commissionAsset')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        symbol = None
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'order': orderId,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "symbol":"TBTCBUSDT",
        #         "orderId":"616376654496877056",
        #         "clientOrderId":"158821382304516955",
        #         "transactTime":"1588213823080",
        #         "price":"0",
        #         "origQty":"1000",
        #         "executedQty":"0",
        #         "status":"NEW",
        #         "timeInForce":"GTC",
        #         "type":"MARKET",
        #         "side":"BUY"
        #     }
        #
        # fetchOrder, fetchOpenOrders, fetchClosedOrders
        #
        # spot
        #
        #     {
        #         "orderId":"616384027202542080",
        #         "clientOrderId":"158821470194414688",
        #         "exchangeId":"301",
        #         "symbol":"TBTCBUSDT",
        #         "price":"0",
        #         "origQty":"0.1",
        #         "executedQty":"0.1",
        #         "cummulativeQuoteQty":"682.606",
        #         "avgPrice":"6826.06",
        #         "status":"FILLED",
        #         "timeInForce":"GTC",
        #         "type":"MARKET",
        #         "side":"SELL",
        #         "stopPrice":"0.0",
        #         "icebergQty":"0.0",
        #         "time":"1588214701974",
        #         "updateTime":"0",
        #         "isWorking":true
        #     }
        #
        # future
        #
        #     {
        #         time: "1588353669383",
        #         updateTime: "0",
        #         orderId: "617549770304599296",
        #         clientOrderId: "test-001",
        #         symbol: "BTC-PERP-REV",
        #         price: "10000",
        #         leverage: "1",
        #         origQty: "100",
        #         executedQty: "0",
        #         avgPrice: "0",
        #         marginLocked: "0",
        #         orderType: "LIMIT",
        #         side: "SELL_OPEN",
        #         fees: [],
        #         timeInForce: "GTC",
        #         status: "CANCELED",
        #         priceType: "INPUT"
        #     }
        #
        #
        id = self.safe_string(order, 'orderId')
        clientOrderId = self.safe_string(order, 'clientOrderId')
        timestamp = self.safe_integer(order, 'time')
        if timestamp is None:
            timestamp = self.safe_integer(order, 'transactTime')
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        type = self.safe_string_lower(order, 'type')
        side = self.safe_string_lower(order, 'side')
        price = self.safe_number(order, 'price')
        average = self.safe_number(order, 'avgPrice')
        amount = None
        cost = self.safe_number(order, 'cummulativeQuoteQty')
        filled = None
        remaining = None
        if type is None:
            type = self.safe_string_lower(order, 'orderType')
            if (market is not None) and market['inverse']:
                cost = self.safe_number(order, 'executedQty')
                amount = None
            if cost == 0.0:
                filled = 0
        else:
            amount = self.safe_number(order, 'origQty')
            if type == 'market':
                price = None
                if side == 'buy':
                    amount = None
            filled = self.safe_number(order, 'executedQty')
            if filled is not None:
                if amount is not None:
                    remaining = amount - filled
        if average == 0.0:
            average = None
        status = self.parse_order_status(self.safe_string(order, 'status'))
        timeInForce = self.safe_string(order, 'timeInForce')
        stopPrice = self.safe_number(order, 'stopPrice')
        result = {
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'average': average,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'trades': None,
            'fee': None,
            'fees': None,
        }
        fees = self.safe_value(order, 'fees', [])
        numFees = len(fees)
        if numFees > 0:
            result['fees'] = []
            for i in range(0, len(fees)):
                feeCost = self.safe_number(fees[i], 'fee')
                if feeCost is not None:
                    feeCurrencyId = self.safe_string(fees[i], 'feeToken')
                    feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
                    result['fees'].append({
                        'cost': feeCost,
                        'currency': feeCurrencyCode,
                    })
        return result

    def parse_order_status(self, status):
        statuses = {
            'NEW': 'open',
            'CANCELED': 'canceled',
            'FILLED': 'closed',
            'PARTIALLY_FILLED': 'open',
            'PENDING_CANCEL': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/' + self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        isPublicContract = (api == 'contract') and ((path == 'insurance') or (path == 'fundingRate'))
        if (api == 'public') or (api == 'quote') or isPublicContract:
            if params:
                url += '?' + self.urlencode(params)
        else:
            timestamp = self.milliseconds()
            self.check_required_credentials()
            request = self.extend({
                'timestamp': timestamp,
            }, query)
            # 准备待签名数据
            auth = self.urlencode(request)
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
            request['signature'] = signature
            headers = {
                'X-BH-APIKEY': self.apiKey,
            }
            if method == 'POST':
                body = self.urlencode(request)
                headers = self.extend({
                    'Content-Type': 'application/x-www-form-urlencoded',
                }, headers)
            else:
                url += '?' + self.urlencode(request)
        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
        if 'code' in response:
            code = self.safe_string(response, 'code')
            if code != '0':
                feedback = self.id + ' ' + body
                self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
                raise ExchangeError(feedback)
