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

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

from ccxt.async_support.base.exchange import Exchange
import math
import json
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
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.errors import OrderNotFound
from ccxt.base.errors import OrderImmediatelyFillable
from ccxt.base.errors import NotSupported
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import ROUND
from ccxt.base.decimal_to_precision import TRUNCATE


class binance(Exchange):

    def describe(self):
        return self.deep_extend(super(binance, self).describe(), {
            'id': 'binance',
            'name': 'Binance',
            'countries': ['JP', 'MT'],  # Japan, Malta
            'rateLimit': 500,
            'certified': True,
            'pro': True,
            # new metainfo interface
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'CORS': False,
                'createOrder': True,
                'fetchBalance': True,
                'fetchBidsAsks': True,
                'fetchClosedOrders': 'emulated',
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingFees': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrders': True,
                'fetchOrderBook': True,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': True,
                'fetchTransactions': False,
                '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/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg',
                'test': {
                    'dapiPublic': 'https://testnet.binancefuture.com/dapi/v1',
                    'dapiPrivate': 'https://testnet.binancefuture.com/dapi/v1',
                    'fapiPublic': 'https://testnet.binancefuture.com/fapi/v1',
                    'fapiPrivate': 'https://testnet.binancefuture.com/fapi/v1',
                    'fapiPrivateV2': 'https://testnet.binancefuture.com/fapi/v2',
                    'public': 'https://testnet.binance.vision/api/v3',
                    'private': 'https://testnet.binance.vision/api/v3',
                    'v3': 'https://testnet.binance.vision/api/v3',
                    'v1': 'https://testnet.binance.vision/api/v1',
                },
                'api': {
                    'wapi': 'https://api.binance.com/wapi/v3',
                    'sapi': 'https://api.binance.com/sapi/v1',
                    'dapiPublic': 'https://dapi.binance.com/dapi/v1',
                    'dapiPrivate': 'https://dapi.binance.com/dapi/v1',
                    'dapiData': 'https://dapi.binance.com/futures/data',
                    'fapiPublic': 'https://fapi.binance.com/fapi/v1',
                    'fapiPrivate': 'https://fapi.binance.com/fapi/v1',
                    'fapiData': 'https://fapi.binance.com/futures/data',
                    'fapiPrivateV2': 'https://fapi.binance.com/fapi/v2',
                    'public': 'https://api.binance.com/api/v3',
                    'private': 'https://api.binance.com/api/v3',
                    'v3': 'https://api.binance.com/api/v3',
                    'v1': 'https://api.binance.com/api/v1',
                },
                'www': 'https://www.binance.com',
                'referral': 'https://www.binance.com/?ref=10205187',
                'doc': [
                    'https://binance-docs.github.io/apidocs/spot/en',
                ],
                'api_management': 'https://www.binance.com/en/usercenter/settings/api-management',
                'fees': 'https://www.binance.com/en/fee/schedule',
            },
            'api': {
                # the API structure below will need 3-layer apidefs
                'sapi': {
                    'get': [
                        'accountSnapshot',
                        # these endpoints require self.apiKey
                        'margin/asset',
                        'margin/pair',
                        'margin/allAssets',
                        'margin/allPairs',
                        'margin/priceIndex',
                        # these endpoints require self.apiKey + self.secret
                        'asset/assetDividend',
                        'asset/transfer',
                        'margin/loan',
                        'margin/repay',
                        'margin/account',
                        'margin/transfer',
                        'margin/interestHistory',
                        'margin/forceLiquidationRec',
                        'margin/order',
                        'margin/openOrders',
                        'margin/allOrders',
                        'margin/myTrades',
                        'margin/maxBorrowable',
                        'margin/maxTransferable',
                        'margin/isolated/transfer',
                        'margin/isolated/account',
                        'margin/isolated/pair',
                        'margin/isolated/allPairs',
                        'futures/transfer',
                        'futures/loan/borrow/history',
                        'futures/loan/repay/history',
                        'futures/loan/wallet',
                        'futures/loan/configs',
                        'futures/loan/calcAdjustLevel',
                        'futures/loan/calcMaxAdjustAmount',
                        'futures/loan/adjustCollateral/history',
                        'futures/loan/liquidationHistory',
                        # https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi
                        'capital/config/getall',  # get networks for withdrawing USDT ERC20 vs USDT Omni
                        'capital/deposit/address',
                        'capital/deposit/hisrec',
                        'capital/deposit/subAddress',
                        'capital/deposit/subHisrec',
                        'capital/withdraw/history',
                        'sub-account/futures/account',
                        'sub-account/futures/accountSummary',
                        'sub-account/futures/positionRisk',
                        'sub-account/futures/internalTransfer',
                        'sub-account/margin/account',
                        'sub-account/margin/accountSummary',
                        'sub-account/spotSummary',
                        'sub-account/status',
                        'sub-account/transfer/subUserHistory',
                        'sub-account/universalTransfer',
                        # lending endpoints
                        'lending/daily/product/list',
                        'lending/daily/userLeftQuota',
                        'lending/daily/userRedemptionQuota',
                        'lending/daily/token/position',
                        'lending/union/account',
                        'lending/union/purchaseRecord',
                        'lending/union/redemptionRecord',
                        'lending/union/interestHistory',
                        'lending/project/list',
                        'lending/project/position/list',
                        # mining endpoints
                        'mining/pub/algoList',
                        'mining/pub/coinList',
                        'mining/worker/detail',
                        'mining/worker/list',
                        'mining/payment/list',
                        'mining/statistics/user/status',
                        'mining/statistics/user/list',
                        # liquid swap endpoints
                        'bswap/pools',
                        'bswap/liquidity',
                        'bswap/liquidityOps',
                        'bswap/quote',
                        'bswap/swap',
                        # leveraged token endpoints
                        'blvt/tokenInfo',
                        'blvt/subscribe/record',
                        'blvt/redeem/record',
                        'blvt/userLimit',
                    ],
                    'post': [
                        'asset/dust',
                        'asset/transfer',
                        'account/disableFastWithdrawSwitch',
                        'account/enableFastWithdrawSwitch',
                        'capital/withdraw/apply',
                        'margin/transfer',
                        'margin/loan',
                        'margin/repay',
                        'margin/order',
                        'margin/isolated/create',
                        'margin/isolated/transfer',
                        'sub-account/margin/transfer',
                        'sub-account/margin/enable',
                        'sub-account/margin/enable',
                        'sub-account/futures/enable',
                        'sub-account/futures/transfer',
                        'sub-account/futures/internalTransfer',
                        'sub-account/transfer/subToSub',
                        'sub-account/transfer/subToMaster',
                        'sub-account/universalTransfer',
                        'userDataStream',
                        'userDataStream/isolated',
                        'futures/transfer',
                        'futures/loan/borrow',
                        'futures/loan/repay',
                        'futures/loan/adjustCollateral',
                        # lending
                        'lending/customizedFixed/purchase',
                        'lending/daily/purchase',
                        'lending/daily/redeem',
                        # liquid swap endpoints
                        'bswap/liquidityAdd',
                        'bswap/liquidityRemove',
                        'bswap/swap',
                        # leveraged token endpoints
                        'blvt/subscribe',
                        'blvt/redeem',
                    ],
                    'put': [
                        'userDataStream',
                        'userDataStream/isolated',
                    ],
                    'delete': [
                        'margin/openOrders',
                        'margin/order',
                        'userDataStream',
                        'userDataStream/isolated',
                    ],
                },
                'wapi': {
                    'post': [
                        'withdraw',
                        'sub-account/transfer',
                    ],
                    'get': [
                        'depositHistory',
                        'withdrawHistory',
                        'depositAddress',
                        'accountStatus',
                        'systemStatus',
                        'apiTradingStatus',
                        'userAssetDribbletLog',
                        'tradeFee',
                        'assetDetail',
                        'sub-account/list',
                        'sub-account/transfer/history',
                        'sub-account/assets',
                    ],
                },
                'dapiPublic': {
                    'get': [
                        'ping',
                        'time',
                        'exchangeInfo',
                        'depth',
                        'trades',
                        'historicalTrades',
                        'aggTrades',
                        'premiumIndex',
                        'fundingRate',
                        'klines',
                        'continuousKlines',
                        'indexPriceKlines',
                        'markPriceKlines',
                        'ticker/24hr',
                        'ticker/price',
                        'ticker/bookTicker',
                        'allForceOrders',
                        'openInterest',
                    ],
                },
                'dapiData': {
                    'get': [
                        'openInterestHist',
                        'topLongShortAccountRatio',
                        'topLongShortPositionRatio',
                        'globalLongShortAccountRatio',
                        'takerBuySellVol',
                        'basis',
                    ],
                },
                'dapiPrivate': {
                    'get': [
                        'positionSide/dual',
                        'order',
                        'openOrder',
                        'openOrders',
                        'allOrders',
                        'balance',
                        'account',
                        'positionMargin/history',
                        'positionRisk',
                        'userTrades',
                        'income',
                        'leverageBracket',
                        'forceOrders',
                        'adlQuantile',
                    ],
                    'post': [
                        'positionSide/dual',
                        'order',
                        'batchOrders',
                        'countdownCancelAll',
                        'leverage',
                        'marginType',
                        'positionMargin',
                        'listenKey',
                    ],
                    'put': [
                        'listenKey',
                    ],
                    'delete': [
                        'order',
                        'allOpenOrders',
                        'batchOrders',
                        'listenKey',
                    ],
                },
                'fapiPublic': {
                    'get': [
                        'ping',
                        'time',
                        'exchangeInfo',
                        'depth',
                        'trades',
                        'historicalTrades',
                        'aggTrades',
                        'klines',
                        'fundingRate',
                        'premiumIndex',
                        'ticker/24hr',
                        'ticker/price',
                        'ticker/bookTicker',
                        'allForceOrders',
                        'openInterest',
                        'indexInfo',
                    ],
                },
                'fapiData': {
                    'get': [
                        'openInterestHist',
                        'topLongShortAccountRatio',
                        'topLongShortPositionRatio',
                        'globalLongShortAccountRatio',
                        'takerlongshortRatio',
                    ],
                },
                'fapiPrivate': {
                    'get': [
                        'allForceOrders',
                        'allOrders',
                        'openOrder',
                        'openOrders',
                        'order',
                        'account',
                        'balance',
                        'leverageBracket',
                        'positionMargin/history',
                        'positionRisk',
                        'positionSide/dual',
                        'userTrades',
                        'income',
                    ],
                    'post': [
                        'batchOrders',
                        'positionSide/dual',
                        'positionMargin',
                        'marginType',
                        'order',
                        'leverage',
                        'listenKey',
                        'countdownCancelAll',
                    ],
                    'put': [
                        'listenKey',
                    ],
                    'delete': [
                        'batchOrders',
                        'order',
                        'allOpenOrders',
                        'listenKey',
                    ],
                },
                'fapiPrivateV2': {
                    'get': [
                        'account',
                        'balance',
                        'positionRisk',
                    ],
                },
                'v3': {
                    'get': [
                        'ticker/price',
                        'ticker/bookTicker',
                    ],
                },
                'public': {
                    'get': [
                        'ping',
                        'time',
                        'depth',
                        'trades',
                        'aggTrades',
                        'historicalTrades',
                        'klines',
                        'ticker/24hr',
                        'ticker/price',
                        'ticker/bookTicker',
                        'exchangeInfo',
                    ],
                    'put': ['userDataStream'],
                    'post': ['userDataStream'],
                    'delete': ['userDataStream'],
                },
                'private': {
                    'get': [
                        'allOrderList',  # oco
                        'openOrderList',  # oco
                        'orderList',  # oco
                        'order',
                        'openOrders',
                        'allOrders',
                        'account',
                        'myTrades',
                    ],
                    'post': [
                        'order/oco',
                        'order',
                        'order/test',
                    ],
                    'delete': [
                        'openOrders',  # added on 2020-04-25 for canceling all open orders per symbol
                        'orderList',  # oco
                        'order',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'taker': 0.001,
                    'maker': 0.001,
                },
            },
            'commonCurrencies': {
                'BCC': 'BCC',  # kept for backward-compatibility https://github.com/ccxt/ccxt/issues/4848
                'YOYO': 'YOYOW',
            },
            # exchange-specific options
            'options': {
                # 'fetchTradesMethod': 'publicGetAggTrades',  # publicGetTrades, publicGetHistoricalTrades
                'defaultTimeInForce': 'GTC',  # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
                'defaultType': 'spot',  # 'spot', 'future', 'margin', 'delivery'
                'hasAlreadyAuthenticatedSuccessfully': False,
                'warnOnFetchOpenOrdersWithoutSymbol': True,
                'recvWindow': 5 * 1000,  # 5 sec, binance default
                'timeDifference': 0,  # the difference between system clock and Binance clock
                'adjustForTimeDifference': False,  # controls the adjustment logic upon instantiation
                'parseOrderToPrecision': False,  # force amounts and costs in parseOrder to precision
                'newOrderRespType': {
                    'market': 'FULL',  # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
                    'limit': 'RESULT',  # we change it from 'ACK' by default to 'RESULT'
                },
                'quoteOrderQty': True,  # whether market orders support amounts in quote currency
            },
            # https://binance-docs.github.io/apidocs/spot/en/#error-codes-2
            'exceptions': {
                'You are not authorized to execute self request.': PermissionDenied,  # {"msg":"You are not authorized to execute self request."}
                'API key does not exist': AuthenticationError,
                'Order would trigger immediately.': OrderImmediatelyFillable,
                'Stop price would trigger immediately.': OrderImmediatelyFillable,  # {"code":-2010,"msg":"Stop price would trigger immediately."}
                'Order would immediately match and take.': OrderImmediatelyFillable,  # {"code":-2010,"msg":"Order would immediately match and take."}
                'Account has insufficient balance for requested action.': InsufficientFunds,
                'Rest API trading is not enabled.': ExchangeNotAvailable,
                "You don't have permission.": PermissionDenied,  # {"msg":"You don't have permission.","success":false}
                'Market is closed.': ExchangeNotAvailable,  # {"code":-1013,"msg":"Market is closed."}
                'Too many requests.': DDoSProtection,  # {"msg":"Too many requests. Please try again later.","success":false}
                '-1000': ExchangeNotAvailable,  # {"code":-1000,"msg":"An unknown error occured while processing the request."}
                '-1001': ExchangeNotAvailable,  # 'Internal error; unable to process your request. Please try again.'
                '-1002': AuthenticationError,  # 'You are not authorized to execute self request.'
                '-1003': RateLimitExceeded,  # {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."}
                '-1013': InvalidOrder,  # createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL
                '-1015': RateLimitExceeded,  # 'Too many new orders; current limit is %s orders per %s.'
                '-1016': ExchangeNotAvailable,  # 'This service is no longer available.',
                '-1020': BadRequest,  # 'This operation is not supported.'
                '-1021': InvalidNonce,  # 'your time is ahead of server'
                '-1022': AuthenticationError,  # {"code":-1022,"msg":"Signature for self request is not valid."}
                '-1100': BadRequest,  # createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'
                '-1101': BadRequest,  # Too many parameters; expected %s and received %s.
                '-1102': BadRequest,  # Param %s or %s must be sent, but both were empty
                '-1103': BadRequest,  # An unknown parameter was sent.
                '-1104': BadRequest,  # Not all sent parameters were read, read 8 parameters but was sent 9
                '-1105': BadRequest,  # Parameter %s was empty.
                '-1106': BadRequest,  # Parameter %s sent when not required.
                '-1111': BadRequest,  # Precision is over the maximum defined for self asset.
                '-1112': InvalidOrder,  # No orders on book for symbol.
                '-1114': BadRequest,  # TimeInForce parameter sent when not required.
                '-1115': BadRequest,  # Invalid timeInForce.
                '-1116': BadRequest,  # Invalid orderType.
                '-1117': BadRequest,  # Invalid side.
                '-1118': BadRequest,  # New client order ID was empty.
                '-1119': BadRequest,  # Original client order ID was empty.
                '-1120': BadRequest,  # Invalid interval.
                '-1121': BadSymbol,  # Invalid symbol.
                '-1125': AuthenticationError,  # This listenKey does not exist.
                '-1127': BadRequest,  # More than %s hours between startTime and endTime.
                '-1128': BadRequest,  # {"code":-1128,"msg":"Combination of optional parameters invalid."}
                '-1130': BadRequest,  # Data sent for paramter %s is not valid.
                '-1131': BadRequest,  # recvWindow must be less than 60000
                '-2010': ExchangeError,  # generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc...
                '-2011': OrderNotFound,  # cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'
                '-2013': OrderNotFound,  # fetchOrder(1, 'BTC/USDT') -> 'Order does not exist'
                '-2014': AuthenticationError,  # {"code":-2014, "msg": "API-key format invalid."}
                '-2015': AuthenticationError,  # "Invalid API-key, IP, or permissions for action."
                '-2019': InsufficientFunds,  # {"code":-2019,"msg":"Margin is insufficient."}
                '-3005': InsufficientFunds,  # {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."}
                '-3008': InsufficientFunds,  # {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."}
                '-3010': ExchangeError,  # {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."}
                '-3022': AccountSuspended,  # You account's trading is banned.
                '-4028': BadRequest,  # {"code":-4028,"msg":"Leverage 100 is not valid"}
            },
        })

    def nonce(self):
        return self.milliseconds() - self.options['timeDifference']

    async def fetch_time(self, params={}):
        type = self.safe_string_2(self.options, 'fetchTime', 'defaultType', 'spot')
        method = 'publicGetTime'
        if type == 'future':
            method = 'fapiPublicGetTime'
        elif type == 'delivery':
            method = 'dapiPublicGetTime'
        response = await getattr(self, method)(params)
        return self.safe_integer(response, 'serverTime')

    async def load_time_difference(self, params={}):
        serverTime = await self.fetch_time(params)
        after = self.milliseconds()
        self.options['timeDifference'] = after - serverTime
        return self.options['timeDifference']

    async def fetch_markets(self, params={}):
        defaultType = self.safe_string_2(self.options, 'fetchMarkets', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        if (type != 'spot') and (type != 'future') and (type != 'margin') and (type != 'delivery'):
            raise ExchangeError(self.id + " does not support '" + type + "' type, set exchange.options['defaultType'] to 'spot', 'margin', 'delivery' or 'future'")  # eslint-disable-line quotes
        method = 'publicGetExchangeInfo'
        if type == 'future':
            method = 'fapiPublicGetExchangeInfo'
        elif type == 'delivery':
            method = 'dapiPublicGetExchangeInfo'
        response = await getattr(self, method)(query)
        #
        # spot / margin
        #
        #     {
        #         "timezone":"UTC",
        #         "serverTime":1575416692969,
        #         "rateLimits":[
        #             {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},
        #             {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":100},
        #             {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":200000}
        #         ],
        #         "exchangeFilters":[],
        #         "symbols":[
        #             {
        #                 "symbol":"ETHBTC",
        #                 "status":"TRADING",
        #                 "baseAsset":"ETH",
        #                 "baseAssetPrecision":8,
        #                 "quoteAsset":"BTC",
        #                 "quotePrecision":8,
        #                 "baseCommissionPrecision":8,
        #                 "quoteCommissionPrecision":8,
        #                 "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"],
        #                 "icebergAllowed":true,
        #                 "ocoAllowed":true,
        #                 "quoteOrderQtyMarketAllowed":true,
        #                 "isSpotTradingAllowed":true,
        #                 "isMarginTradingAllowed":true,
        #                 "filters":[
        #                     {"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"},
        #                     {"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5},
        #                     {"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"},
        #                     {"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5},
        #                     {"filterType":"ICEBERG_PARTS","limit":10},
        #                     {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"63100.00000000","stepSize":"0.00000000"},
        #                     {"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5}
        #                 ]
        #             },
        #         ],
        #     }
        #
        # futures/usdt-margined(fapi)
        #
        #     {
        #         "timezone":"UTC",
        #         "serverTime":1575417244353,
        #         "rateLimits":[
        #             {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},
        #             {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200}
        #         ],
        #         "exchangeFilters":[],
        #         "symbols":[
        #             {
        #                 "symbol":"BTCUSDT",
        #                 "status":"TRADING",
        #                 "maintMarginPercent":"2.5000",
        #                 "requiredMarginPercent":"5.0000",
        #                 "baseAsset":"BTC",
        #                 "quoteAsset":"USDT",
        #                 "pricePrecision":2,
        #                 "quantityPrecision":3,
        #                 "baseAssetPrecision":8,
        #                 "quotePrecision":8,
        #                 "filters":[
        #                     {"minPrice":"0.01","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.01"},
        #                     {"stepSize":"0.001","filterType":"LOT_SIZE","maxQty":"1000","minQty":"0.001"},
        #                     {"stepSize":"0.001","filterType":"MARKET_LOT_SIZE","maxQty":"1000","minQty":"0.001"},
        #                     {"limit":200,"filterType":"MAX_NUM_ORDERS"},
        #                     {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
        #                 ],
        #                 "orderTypes":["LIMIT","MARKET","STOP"],
        #                 "timeInForce":["GTC","IOC","FOK","GTX"]
        #             }
        #         ]
        #     }
        #
        # delivery/coin-margined(dapi)
        #
        #     {
        #         "timezone": "UTC",
        #         "serverTime": 1597667052958,
        #         "rateLimits": [
        #             {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000},
        #             {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":6000}
        #         ],
        #         "exchangeFilters": [],
        #         "symbols": [
        #             {
        #                 "symbol": "BTCUSD_200925",
        #                 "pair": "BTCUSD",
        #                 "contractType": "CURRENT_QUARTER",
        #                 "deliveryDate": 1601020800000,
        #                 "onboardDate": 1590739200000,
        #                 "contractStatus": "TRADING",
        #                 "contractSize": 100,
        #                 "marginAsset": "BTC",
        #                 "maintMarginPercent": "2.5000",
        #                 "requiredMarginPercent": "5.0000",
        #                 "baseAsset": "BTC",
        #                 "quoteAsset": "USD",
        #                 "pricePrecision": 1,
        #                 "quantityPrecision": 0,
        #                 "baseAssetPrecision": 8,
        #                 "quotePrecision": 8,
        #                 "equalQtyPrecision": 4,
        #                 "filters": [
        #                     {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"},
        #                     {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"},
        #                     {"stepSize":"0","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"},
        #                     {"limit":200,"filterType":"MAX_NUM_ORDERS"},
        #                     {"multiplierDown":"0.9500","multiplierUp":"1.0500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
        #                 ],
        #                 "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"],
        #                 "timeInForce": ["GTC","IOC","FOK","GTX"]
        #             },
        #             {
        #                 "symbol": "BTCUSD_PERP",
        #                 "pair": "BTCUSD",
        #                 "contractType": "PERPETUAL",
        #                 "deliveryDate": 4133404800000,
        #                 "onboardDate": 1596006000000,
        #                 "contractStatus": "TRADING",
        #                 "contractSize": 100,
        #                 "marginAsset": "BTC",
        #                 "maintMarginPercent": "2.5000",
        #                 "requiredMarginPercent": "5.0000",
        #                 "baseAsset": "BTC",
        #                 "quoteAsset": "USD",
        #                 "pricePrecision": 1,
        #                 "quantityPrecision": 0,
        #                 "baseAssetPrecision": 8,
        #                 "quotePrecision": 8,
        #                 "equalQtyPrecision": 4,
        #                 "filters": [
        #                     {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"},
        #                     {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"},
        #                     {"stepSize":"1","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"},
        #                     {"limit":200,"filterType":"MAX_NUM_ORDERS"},
        #                     {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
        #                 ],
        #                 "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"],
        #                 "timeInForce": ["GTC","IOC","FOK","GTX"]
        #             }
        #         ]
        #     }
        #
        if self.options['adjustForTimeDifference']:
            await self.load_time_difference()
        markets = self.safe_value(response, 'symbols')
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            spot = (type == 'spot')
            future = (type == 'future')
            delivery = (type == 'delivery')
            id = self.safe_string(market, 'symbol')
            lowercaseId = self.safe_string_lower(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)
            contractType = self.safe_string(market, 'contractType')
            idSymbol = (future or delivery) and (contractType != 'PERPETUAL')
            symbol = id if idSymbol else (base + '/' + quote)
            filters = self.safe_value(market, 'filters', [])
            filtersByType = self.index_by(filters, 'filterType')
            precision = {
                'base': self.safe_integer(market, 'baseAssetPrecision'),
                'quote': self.safe_integer(market, 'quotePrecision'),
                'amount': self.safe_integer(market, 'baseAssetPrecision'),
                'price': self.safe_integer(market, 'quotePrecision'),
            }
            status = self.safe_string_2(market, 'status', 'contractStatus')
            active = (status == 'TRADING')
            margin = self.safe_value(market, 'isMarginTradingAllowed', future or delivery)
            entry = {
                'id': id,
                'lowercaseId': lowercaseId,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'info': market,
                'type': type,
                'spot': spot,
                'margin': margin,
                'future': future,
                'delivery': delivery,
                'active': active,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision['amount']),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
            }
            if 'PRICE_FILTER' in filtersByType:
                filter = self.safe_value(filtersByType, 'PRICE_FILTER', {})
                # PRICE_FILTER reports zero values for maxPrice
                # since they updated filter types in November 2018
                # https://github.com/ccxt/ccxt/issues/4286
                # therefore limits['price']['max'] doesn't have any meaningful value except None
                entry['limits']['price'] = {
                    'min': self.safe_float(filter, 'minPrice'),
                    'max': None,
                }
                maxPrice = self.safe_float(filter, 'maxPrice')
                if (maxPrice is not None) and (maxPrice > 0):
                    entry['limits']['price']['max'] = maxPrice
                entry['precision']['price'] = self.precision_from_string(filter['tickSize'])
            if 'LOT_SIZE' in filtersByType:
                filter = self.safe_value(filtersByType, 'LOT_SIZE', {})
                stepSize = self.safe_string(filter, 'stepSize')
                entry['precision']['amount'] = self.precision_from_string(stepSize)
                entry['limits']['amount'] = {
                    'min': self.safe_float(filter, 'minQty'),
                    'max': self.safe_float(filter, 'maxQty'),
                }
            if 'MARKET_LOT_SIZE' in filtersByType:
                filter = self.safe_value(filtersByType, 'MARKET_LOT_SIZE', {})
                entry['limits']['market'] = {
                    'min': self.safe_float(filter, 'minQty'),
                    'max': self.safe_float(filter, 'maxQty'),
                }
            if 'MIN_NOTIONAL' in filtersByType:
                filter = self.safe_value(filtersByType, 'MIN_NOTIONAL', {})
                entry['limits']['cost']['min'] = self.safe_float_2(filter, 'minNotional', 'notional')
            result.append(entry)
        return result

    def calculate_fee(self, symbol, type, side, amount, price, takerOrMaker='taker', params={}):
        market = self.markets[symbol]
        key = 'quote'
        rate = market[takerOrMaker]
        cost = amount * rate
        precision = market['precision']['price']
        if side == 'sell':
            cost *= price
        else:
            key = 'base'
            precision = market['precision']['amount']
        cost = self.decimal_to_precision(cost, ROUND, precision, self.precisionMode)
        return {
            'type': takerOrMaker,
            'currency': market[key],
            'rate': rate,
            'cost': float(cost),
        }

    async def fetch_balance(self, params={}):
        await self.load_markets()
        defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        method = 'privateGetAccount'
        if type == 'future':
            options = self.safe_value(self.options, 'future', {})
            fetchBalanceOptions = self.safe_value(options, 'fetchBalance', {})
            method = self.safe_string(fetchBalanceOptions, 'method', 'fapiPrivateV2GetAccount')
        elif type == 'delivery':
            options = self.safe_value(self.options, 'delivery', {})
            fetchBalanceOptions = self.safe_value(options, 'fetchBalance', {})
            method = self.safe_string(fetchBalanceOptions, 'method', 'dapiPrivateGetAccount')
        elif type == 'margin':
            method = 'sapiGetMarginAccount'
        query = self.omit(params, 'type')
        response = await getattr(self, method)(query)
        #
        # spot
        #
        #     {
        #         makerCommission: 10,
        #         takerCommission: 10,
        #         buyerCommission: 0,
        #         sellerCommission: 0,
        #         canTrade: True,
        #         canWithdraw: True,
        #         canDeposit: True,
        #         updateTime: 1575357359602,
        #         accountType: "MARGIN",
        #         balances: [
        #             {asset: "BTC", free: "0.00219821", locked: "0.00000000"  },
        #         ]
        #     }
        #
        # margin
        #
        #     {
        #         "borrowEnabled":true,
        #         "marginLevel":"999.00000000",
        #         "totalAssetOfBtc":"0.00000000",
        #         "totalLiabilityOfBtc":"0.00000000",
        #         "totalNetAssetOfBtc":"0.00000000",
        #         "tradeEnabled":true,
        #         "transferEnabled":true,
        #         "userAssets":[
        #             {"asset":"MATIC","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"},
        #             {"asset":"VET","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"},
        #             {"asset":"USDT","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"}
        #         ],
        #     }
        #
        # futures(fapi)
        #
        #     fapiPrivateGetAccount
        #
        #     {
        #         "feeTier":0,
        #         "canTrade":true,
        #         "canDeposit":true,
        #         "canWithdraw":true,
        #         "updateTime":0,
        #         "totalInitialMargin":"0.00000000",
        #         "totalMaintMargin":"0.00000000",
        #         "totalWalletBalance":"4.54000000",
        #         "totalUnrealizedProfit":"0.00000000",
        #         "totalMarginBalance":"4.54000000",
        #         "totalPositionInitialMargin":"0.00000000",
        #         "totalOpenOrderInitialMargin":"0.00000000",
        #         "maxWithdrawAmount":"4.54000000",
        #         "assets":[
        #             {
        #                 "asset":"USDT",
        #                 "walletBalance":"4.54000000",
        #                 "unrealizedProfit":"0.00000000",
        #                 "marginBalance":"4.54000000",
        #                 "maintMargin":"0.00000000",
        #                 "initialMargin":"0.00000000",
        #                 "positionInitialMargin":"0.00000000",
        #                 "openOrderInitialMargin":"0.00000000",
        #                 "maxWithdrawAmount":"4.54000000"
        #             }
        #         ],
        #         "positions":[
        #             {
        #                 "symbol":"BTCUSDT",
        #                 "initialMargin":"0.00000",
        #                 "maintMargin":"0.00000",
        #                 "unrealizedProfit":"0.00000000",
        #                 "positionInitialMargin":"0.00000",
        #                 "openOrderInitialMargin":"0.00000"
        #             }
        #         ]
        #     }
        #
        #     fapiPrivateV2GetAccount
        #
        #     {
        #         "feeTier":0,
        #         "canTrade":true,
        #         "canDeposit":true,
        #         "canWithdraw":true,
        #         "updateTime":0,
        #         "totalInitialMargin":"0.00000000",
        #         "totalMaintMargin":"0.00000000",
        #         "totalWalletBalance":"0.00000000",
        #         "totalUnrealizedProfit":"0.00000000",
        #         "totalMarginBalance":"0.00000000",
        #         "totalPositionInitialMargin":"0.00000000",
        #         "totalOpenOrderInitialMargin":"0.00000000",
        #         "totalCrossWalletBalance":"0.00000000",
        #         "totalCrossUnPnl":"0.00000000",
        #         "availableBalance":"0.00000000",
        #         "maxWithdrawAmount":"0.00000000",
        #         "assets":[
        #             {
        #                 "asset":"BNB",
        #                 "walletBalance":"0.01000000",
        #                 "unrealizedProfit":"0.00000000",
        #                 "marginBalance":"0.01000000",
        #                 "maintMargin":"0.00000000",
        #                 "initialMargin":"0.00000000",
        #                 "positionInitialMargin":"0.00000000",
        #                 "openOrderInitialMargin":"0.00000000",
        #                 "maxWithdrawAmount":"0.01000000",
        #                 "crossWalletBalance":"0.01000000",
        #                 "crossUnPnl":"0.00000000",
        #                 "availableBalance":"0.01000000"
        #             }
        #         ],
        #         "positions":[
        #             {
        #                 "symbol":"BTCUSDT",
        #                 "initialMargin":"0",
        #                 "maintMargin":"0",
        #                 "unrealizedProfit":"0.00000000",
        #                 "positionInitialMargin":"0",
        #                 "openOrderInitialMargin":"0",
        #                 "leverage":"20",
        #                 "isolated":false,
        #                 "entryPrice":"0.00000",
        #                 "maxNotional":"5000000",
        #                 "positionSide":"BOTH"
        #             },
        #         ]
        #     }
        #
        #     fapiPrivateV2GetBalance
        #
        #     [
        #         {
        #             "accountAlias":"FzFzXquXXqoC",
        #             "asset":"BNB",
        #             "balance":"0.01000000",
        #             "crossWalletBalance":"0.01000000",
        #             "crossUnPnl":"0.00000000",
        #             "availableBalance":"0.01000000",
        #             "maxWithdrawAmount":"0.01000000"
        #         }
        #     ]
        #
        result = {'info': response}
        if (type == 'spot') or (type == 'margin'):
            balances = self.safe_value_2(response, 'balances', 'userAssets', [])
            for i in range(0, len(balances)):
                balance = balances[i]
                currencyId = self.safe_string(balance, 'asset')
                code = self.safe_currency_code(currencyId)
                account = self.account()
                account['free'] = self.safe_float(balance, 'free')
                account['used'] = self.safe_float(balance, 'locked')
                result[code] = account
        else:
            balances = response
            if not isinstance(response, list):
                balances = self.safe_value(response, 'assets', [])
            for i in range(0, len(balances)):
                balance = balances[i]
                currencyId = self.safe_string(balance, 'asset')
                code = self.safe_currency_code(currencyId)
                account = self.account()
                account['free'] = self.safe_float(balance, 'availableBalance')
                account['used'] = self.safe_float(balance, 'initialMargin')
                account['total'] = self.safe_float_2(balance, 'marginBalance', 'balance')
                result[code] = account
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 100, max 5000, see https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#order-book
        method = 'publicGetDepth'
        if market['future']:
            method = 'fapiPublicGetDepth'
        elif market['delivery']:
            method = 'dapiPublicGetDepth'
        response = await getattr(self, method)(self.extend(request, params))
        orderbook = self.parse_order_book(response)
        orderbook['nonce'] = self.safe_integer(response, 'lastUpdateId')
        return orderbook

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         symbol: 'ETHBTC',
        #         priceChange: '0.00068700',
        #         priceChangePercent: '2.075',
        #         weightedAvgPrice: '0.03342681',
        #         prevClosePrice: '0.03310300',
        #         lastPrice: '0.03378900',
        #         lastQty: '0.07700000',
        #         bidPrice: '0.03378900',
        #         bidQty: '7.16800000',
        #         askPrice: '0.03379000',
        #         askQty: '24.00000000',
        #         openPrice: '0.03310200',
        #         highPrice: '0.03388900',
        #         lowPrice: '0.03306900',
        #         volume: '205478.41000000',
        #         quoteVolume: '6868.48826294',
        #         openTime: 1601469986932,
        #         closeTime: 1601556386932,
        #         firstId: 196098772,
        #         lastId: 196186315,
        #         count: 87544
        #     }
        #
        timestamp = self.safe_integer(ticker, 'closeTime')
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_float(ticker, 'lastPrice')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'highPrice'),
            'low': self.safe_float(ticker, 'lowPrice'),
            'bid': self.safe_float(ticker, 'bidPrice'),
            'bidVolume': self.safe_float(ticker, 'bidQty'),
            'ask': self.safe_float(ticker, 'askPrice'),
            'askVolume': self.safe_float(ticker, 'askQty'),
            'vwap': self.safe_float(ticker, 'weightedAvgPrice'),
            'open': self.safe_float(ticker, 'openPrice'),
            'close': last,
            'last': last,
            'previousClose': self.safe_float(ticker, 'prevClosePrice'),  # previous day close
            'change': self.safe_float(ticker, 'priceChange'),
            'percentage': self.safe_float(ticker, 'priceChangePercent'),
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': self.safe_float(ticker, 'quoteVolume'),
            'info': ticker,
        }

    async def fetch_status(self, params={}):
        response = await self.wapiGetSystemStatus(params)
        status = self.safe_value(response, 'status')
        if status is not None:
            status = 'ok' if (status == 0) else 'maintenance'
            self.status = self.extend(self.status, {
                'status': status,
                'updated': self.milliseconds(),
            })
        return self.status

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        method = 'publicGetTicker24hr'
        if market['future']:
            method = 'fapiPublicGetTicker24hr'
        elif market['delivery']:
            method = 'dapiPublicGetTicker24hr'
        response = await getattr(self, method)(self.extend(request, params))
        if isinstance(response, list):
            firstTicker = self.safe_value(response, 0, {})
            return self.parse_ticker(firstTicker, market)
        return self.parse_ticker(response, market)

    def parse_tickers(self, rawTickers, symbols=None):
        tickers = []
        for i in range(0, len(rawTickers)):
            tickers.append(self.parse_ticker(rawTickers[i]))
        return self.filter_by_array(tickers, 'symbol', symbols)

    async def fetch_bids_asks(self, symbols=None, params={}):
        await self.load_markets()
        defaultType = self.safe_string_2(self.options, 'fetchBidsAsks', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        method = None
        if type == 'future':
            method = 'fapiPublicGetTickerBookTicker'
        elif type == 'delivery':
            method = 'dapiPublicGetTickerBookTicker'
        else:
            method = 'publicGetTickerBookTicker'
        response = await getattr(self, method)(query)
        return self.parse_tickers(response, symbols)

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        defaultType = self.safe_string_2(self.options, 'fetchTickers', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        defaultMethod = None
        if type == 'future':
            defaultMethod = 'fapiPublicGetTicker24hr'
        elif type == 'delivery':
            defaultMethod = 'dapiPublicGetTicker24hr'
        else:
            defaultMethod = 'publicGetTicker24hr'
        method = self.safe_string(self.options, 'fetchTickersMethod', defaultMethod)
        response = await getattr(self, method)(query)
        return self.parse_tickers(response, symbols)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1591478520000,
        #         "0.02501300",
        #         "0.02501800",
        #         "0.02500000",
        #         "0.02500000",
        #         "22.19000000",
        #         1591478579999,
        #         "0.55490906",
        #         40,
        #         "10.92900000",
        #         "0.27336462",
        #         "0"
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_float(ohlcv, 1),
            self.safe_float(ohlcv, 2),
            self.safe_float(ohlcv, 3),
            self.safe_float(ohlcv, 4),
            self.safe_float(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        # binance docs say that the default limit 500, max 1500 for futures, max 1000 for spot markets
        # the reality is that the time range wider than 500 candles won't work right
        defaultLimit = 500
        maxLimit = 1500
        limit = defaultLimit if (limit is None) else min(limit, maxLimit)
        request = {
            'symbol': market['id'],
            'interval': self.timeframes[timeframe],
            'limit': limit,
        }
        duration = self.parse_timeframe(timeframe)
        if since is not None:
            request['startTime'] = since
            endTime = self.sum(since, limit * duration * 1000 - 1)
            now = self.milliseconds()
            request['endTime'] = min(now, endTime)
        method = 'publicGetKlines'
        if market['future']:
            method = 'fapiPublicGetKlines'
        elif market['delivery']:
            method = 'dapiPublicGetKlines'
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     [
        #         [1591478520000,"0.02501300","0.02501800","0.02500000","0.02500000","22.19000000",1591478579999,"0.55490906",40,"10.92900000","0.27336462","0"],
        #         [1591478580000,"0.02499600","0.02500900","0.02499400","0.02500300","21.34700000",1591478639999,"0.53370468",24,"7.53800000","0.18850725","0"],
        #         [1591478640000,"0.02500800","0.02501100","0.02500300","0.02500800","154.14200000",1591478699999,"3.85405839",97,"5.32300000","0.13312641","0"],
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_trade(self, trade, market=None):
        if 'isDustTrade' in trade:
            return self.parse_dust_trade(trade, market)
        #
        # aggregate trades
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
        #
        #     {
        #         "a": 26129,         # Aggregate tradeId
        #         "p": "0.01633102",  # Price
        #         "q": "4.70443515",  # Quantity
        #         "f": 27781,         # First tradeId
        #         "l": 27781,         # Last tradeId
        #         "T": 1498793709153,  # Timestamp
        #         "m": True,          # Was the buyer the maker?
        #         "M": True           # Was the trade the best price match?
        #     }
        #
        # recent public trades and old public trades
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data
        #
        #     {
        #         "id": 28457,
        #         "price": "4.00000100",
        #         "qty": "12.00000000",
        #         "time": 1499865549590,
        #         "isBuyerMaker": True,
        #         "isBestMatch": True
        #     }
        #
        # private trades
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data
        #
        #     {
        #         "symbol": "BNBBTC",
        #         "id": 28457,
        #         "orderId": 100234,
        #         "price": "4.00000100",
        #         "qty": "12.00000000",
        #         "commission": "10.10000000",
        #         "commissionAsset": "BNB",
        #         "time": 1499865549590,
        #         "isBuyer": True,
        #         "isMaker": False,
        #         "isBestMatch": True
        #     }
        #
        # futures trades
        # https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data
        #
        #     {
        #       "accountId": 20,
        #       "buyer": False,
        #       "commission": "-0.07819010",
        #       "commissionAsset": "USDT",
        #       "counterPartyId": 653,
        #       "id": 698759,
        #       "maker": False,
        #       "orderId": 25851813,
        #       "price": "7819.01",
        #       "qty": "0.002",
        #       "quoteQty": "0.01563",
        #       "realizedPnl": "-0.91539999",
        #       "side": "SELL",
        #       "symbol": "BTCUSDT",
        #       "time": 1569514978020
        #     }
        #
        timestamp = self.safe_integer_2(trade, 'T', 'time')
        price = self.safe_float_2(trade, 'p', 'price')
        amount = self.safe_float_2(trade, 'q', 'qty')
        id = self.safe_string_2(trade, 'a', 'id')
        side = None
        orderId = self.safe_string(trade, 'orderId')
        if 'm' in trade:
            side = 'sell' if trade['m'] else 'buy'  # self is reversed intentionally
        elif 'isBuyerMaker' in trade:
            side = 'sell' if trade['isBuyerMaker'] else 'buy'
        elif 'side' in trade:
            side = self.safe_string_lower(trade, 'side')
        else:
            if 'isBuyer' in trade:
                side = 'buy' if trade['isBuyer'] else 'sell'  # self is a True side
        fee = None
        if 'commission' in trade:
            fee = {
                'cost': self.safe_float(trade, 'commission'),
                'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAsset')),
            }
        takerOrMaker = None
        if 'isMaker' in trade:
            takerOrMaker = 'maker' if trade['isMaker'] else 'taker'
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        cost = None
        if (price is not None) and (amount is not None):
            cost = price * amount
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            # 'fromId': 123,    # ID to get aggregate trades from INCLUSIVE.
            # 'startTime': 456,  # Timestamp in ms to get aggregate trades from INCLUSIVE.
            # 'endTime': 789,   # Timestamp in ms to get aggregate trades until INCLUSIVE.
            # 'limit': 500,     # default = 500, maximum = 1000
        }
        defaultType = self.safe_string_2(self.options, 'fetchTrades', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        defaultMethod = None
        if type == 'future':
            defaultMethod = 'fapiPublicGetAggTrades'
        elif type == 'delivery':
            defaultMethod = 'dapiPublicGetAggTrades'
        else:
            defaultMethod = 'publicGetAggTrades'
        method = self.safe_string(self.options, 'fetchTradesMethod', defaultMethod)
        if method == 'publicGetAggTrades':
            if since is not None:
                request['startTime'] = since
                # https://github.com/ccxt/ccxt/issues/6400
                # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
                request['endTime'] = self.sum(since, 3600000)
            if type == 'future':
                method = 'fapiPublicGetAggTrades'
            elif type == 'delivery':
                method = 'dapiPublicGetAggTrades'
        elif method == 'publicGetHistoricalTrades':
            if type == 'future':
                method = 'fapiPublicGetHistoricalTrades'
            elif type == 'delivery':
                method = 'dapiPublicGetHistoricalTrades'
        if limit is not None:
            request['limit'] = limit  # default = 500, maximum = 1000
        #
        # Caveats:
        # - default limit(500) applies only if no other parameters set, trades up
        #   to the maximum limit may be returned to satisfy other parameters
        # - if both limit and time window is set and time window contains more
        #   trades than the limit then the last trades from the window are returned
        # - 'tradeId' accepted and returned by self method is "aggregate" trade id
        #   which is different from actual trade id
        # - setting both fromId and time window results in error
        response = await getattr(self, method)(self.extend(request, query))
        #
        # aggregate trades
        #
        #     [
        #         {
        #             "a": 26129,         # Aggregate tradeId
        #             "p": "0.01633102",  # Price
        #             "q": "4.70443515",  # Quantity
        #             "f": 27781,         # First tradeId
        #             "l": 27781,         # Last tradeId
        #             "T": 1498793709153,  # Timestamp
        #             "m": True,          # Was the buyer the maker?
        #             "M": True           # Was the trade the best price match?
        #         }
        #     ]
        #
        # recent public trades and historical public trades
        #
        #     [
        #         {
        #             "id": 28457,
        #             "price": "4.00000100",
        #             "qty": "12.00000000",
        #             "time": 1499865549590,
        #             "isBuyerMaker": True,
        #             "isBestMatch": True
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_order_status(self, status):
        statuses = {
            'NEW': 'open',
            'PARTIALLY_FILLED': 'open',
            'FILLED': 'closed',
            'CANCELED': 'canceled',
            'PENDING_CANCEL': 'canceling',  # currently unused
            'REJECTED': 'rejected',
            'EXPIRED': 'expired',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        #  spot
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "orderId": 1,
        #         "clientOrderId": "myOrder1",
        #         "price": "0.1",
        #         "origQty": "1.0",
        #         "executedQty": "0.0",
        #         "cummulativeQuoteQty": "0.0",
        #         "status": "NEW",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #         "stopPrice": "0.0",
        #         "icebergQty": "0.0",
        #         "time": 1499827319559,
        #         "updateTime": 1499827319559,
        #         "isWorking": True
        #     }
        #
        #  futures
        #
        #     {
        #         "symbol": "BTCUSDT",
        #         "orderId": 1,
        #         "clientOrderId": "myOrder1",
        #         "price": "0.1",
        #         "origQty": "1.0",
        #         "executedQty": "1.0",
        #         "cumQuote": "10.0",
        #         "status": "NEW",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #         "stopPrice": "0.0",
        #         "updateTime": 1499827319559
        #     }
        #
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        timestamp = None
        if 'time' in order:
            timestamp = self.safe_integer(order, 'time')
        elif 'transactTime' in order:
            timestamp = self.safe_integer(order, 'transactTime')
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'origQty')
        filled = self.safe_float(order, 'executedQty')
        remaining = None
        # - Spot/Margin market: cummulativeQuoteQty
        # - Futures market: cumQuote.
        #   Note self is not the actual cost, since Binance futures uses leverage to calculate margins.
        cost = self.safe_float_2(order, 'cummulativeQuoteQty', 'cumQuote')
        if filled is not None:
            if amount is not None:
                remaining = amount - filled
                if self.options['parseOrderToPrecision']:
                    remaining = float(self.amount_to_precision(symbol, remaining))
                remaining = max(remaining, 0.0)
            if price is not None:
                if cost is None:
                    cost = price * filled
        id = self.safe_string(order, 'orderId')
        type = self.safe_string_lower(order, 'type')
        if type == 'market':
            if price == 0.0:
                if (cost is not None) and (filled is not None):
                    if (cost > 0) and (filled > 0):
                        price = cost / filled
                        if self.options['parseOrderToPrecision']:
                            price = float(self.price_to_precision(symbol, price))
        elif type == 'limit_maker':
            type = 'limit'
        side = self.safe_string_lower(order, 'side')
        fee = None
        trades = None
        fills = self.safe_value(order, 'fills')
        if fills is not None:
            trades = self.parse_trades(fills, market)
            numTrades = len(trades)
            if numTrades > 0:
                cost = trades[0]['cost']
                fee = {
                    'cost': trades[0]['fee']['cost'],
                    'currency': trades[0]['fee']['currency'],
                }
                for i in range(1, len(trades)):
                    cost = self.sum(cost, trades[i]['cost'])
                    fee['cost'] = self.sum(fee['cost'], trades[i]['fee']['cost'])
        average = None
        if cost is not None:
            if filled:
                average = cost / filled
                if self.options['parseOrderToPrecision']:
                    average = float(self.price_to_precision(symbol, average))
            if self.options['parseOrderToPrecision']:
                cost = float(self.cost_to_precision(symbol, cost))
        clientOrderId = self.safe_string(order, 'clientOrderId')
        timeInForce = self.safe_string(order, 'timeInForce')
        postOnly = (type == 'limit_maker') or (timeInForce == 'GTX')
        stopPrice = self.safe_float(order, 'stopPrice')
        return {
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': trades,
        }

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        defaultType = self.safe_string_2(self.options, 'createOrder', 'defaultType', market['type'])
        orderType = self.safe_string(params, 'type', defaultType)
        clientOrderId = self.safe_string_2(params, 'newClientOrderId', 'clientOrderId')
        params = self.omit(params, ['type', 'newClientOrderId', 'clientOrderId'])
        method = 'privatePostOrder'
        if orderType == 'future':
            method = 'fapiPrivatePostOrder'
        elif orderType == 'delivery':
            method = 'dapiPrivatePostOrder'
        elif orderType == 'margin':
            method = 'sapiPostMarginOrder'
        # the next 5 lines are added to support for testing orders
        if market['spot']:
            test = self.safe_value(params, 'test', False)
            if test:
                method += 'Test'
            params = self.omit(params, 'test')
        uppercaseType = type.upper()
        validOrderTypes = self.safe_value(market['info'], 'orderTypes')
        if not self.in_array(uppercaseType, validOrderTypes):
            raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type in ' + market['type'] + ' market ' + symbol)
        request = {
            'symbol': market['id'],
            'type': uppercaseType,
            'side': side.upper(),
        }
        if clientOrderId is not None:
            request['newClientOrderId'] = clientOrderId
        if market['spot']:
            request['newOrderRespType'] = self.safe_value(self.options['newOrderRespType'], type, 'RESULT')  # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
        # additional required fields depending on the order type
        timeInForceIsRequired = False
        priceIsRequired = False
        stopPriceIsRequired = False
        quantityIsRequired = False
        #
        # spot/margin
        #
        #     LIMIT                timeInForce, quantity, price
        #     MARKET               quantity or quoteOrderQty
        #     STOP_LOSS            quantity, stopPrice
        #     STOP_LOSS_LIMIT      timeInForce, quantity, price, stopPrice
        #     TAKE_PROFIT          quantity, stopPrice
        #     TAKE_PROFIT_LIMIT    timeInForce, quantity, price, stopPrice
        #     LIMIT_MAKER          quantity, price
        #
        # futures
        #
        #     LIMIT                timeInForce, quantity, price
        #     MARKET               quantity
        #     STOP/TAKE_PROFIT     quantity, price, stopPrice
        #     STOP_MARKET          stopPrice
        #     TAKE_PROFIT_MARKET   stopPrice
        #     TRAILING_STOP_MARKET callbackRate
        #
        if uppercaseType == 'MARKET':
            quoteOrderQty = self.safe_value(self.options, 'quoteOrderQty', False)
            if quoteOrderQty:
                quoteOrderQty = self.safe_float(params, 'quoteOrderQty')
                precision = market['precision']['price']
                if quoteOrderQty is not None:
                    request['quoteOrderQty'] = self.decimal_to_precision(quoteOrderQty, TRUNCATE, precision, self.precisionMode)
                    params = self.omit(params, 'quoteOrderQty')
                elif price is not None:
                    request['quoteOrderQty'] = self.decimal_to_precision(amount * price, TRUNCATE, precision, self.precisionMode)
                else:
                    quantityIsRequired = True
            else:
                quantityIsRequired = True
        elif uppercaseType == 'LIMIT':
            priceIsRequired = True
            timeInForceIsRequired = True
            quantityIsRequired = True
        elif (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT'):
            stopPriceIsRequired = True
            quantityIsRequired = True
            if market['future']:
                priceIsRequired = True
        elif (uppercaseType == 'STOP_LOSS_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
            quantityIsRequired = True
            stopPriceIsRequired = True
            priceIsRequired = True
            timeInForceIsRequired = True
        elif uppercaseType == 'LIMIT_MAKER':
            priceIsRequired = True
            quantityIsRequired = True
        elif uppercaseType == 'STOP':
            quantityIsRequired = True
            stopPriceIsRequired = True
            priceIsRequired = True
        elif (uppercaseType == 'STOP_MARKET') or (uppercaseType == 'TAKE_PROFIT_MARKET'):
            closePosition = self.safe_value(params, 'closePosition')
            if closePosition is None:
                quantityIsRequired = True
            stopPriceIsRequired = True
        elif uppercaseType == 'TRAILING_STOP_MARKET':
            quantityIsRequired = True
            callbackRate = self.safe_float(params, 'callbackRate')
            if callbackRate is None:
                raise InvalidOrder(self.id + ' createOrder() requires a callbackRate extra param for a ' + type + ' order')
        if quantityIsRequired:
            request['quantity'] = self.amount_to_precision(symbol, amount)
        if priceIsRequired:
            if price is None:
                raise InvalidOrder(self.id + ' createOrder() requires a price argument for a ' + type + ' order')
            request['price'] = self.price_to_precision(symbol, price)
        if timeInForceIsRequired:
            request['timeInForce'] = self.options['defaultTimeInForce']  # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
        if stopPriceIsRequired:
            stopPrice = self.safe_float(params, 'stopPrice')
            if stopPrice is None:
                raise InvalidOrder(self.id + ' createOrder() requires a stopPrice extra param for a ' + type + ' order')
            else:
                params = self.omit(params, 'stopPrice')
                request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
        response = await getattr(self, method)(self.extend(request, params))
        return self.parse_order(response, market)

    async def fetch_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        defaultType = self.safe_string_2(self.options, 'fetchOrder', 'defaultType', market['type'])
        type = self.safe_string(params, 'type', defaultType)
        method = 'privateGetOrder'
        if type == 'future':
            method = 'fapiPrivateGetOrder'
        elif type == 'delivery':
            method = 'dapiPrivateGetOrder'
        elif type == 'margin':
            method = 'sapiGetMarginOrder'
        request = {
            'symbol': market['id'],
        }
        clientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
        if clientOrderId is not None:
            request['origClientOrderId'] = clientOrderId
        else:
            request['orderId'] = id
        query = self.omit(params, ['type', 'clientOrderId', 'origClientOrderId'])
        response = await getattr(self, method)(self.extend(request, query))
        return self.parse_order(response, market)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        defaultType = self.safe_string_2(self.options, 'fetchOrders', 'defaultType', market['type'])
        type = self.safe_string(params, 'type', defaultType)
        method = 'privateGetAllOrders'
        if type == 'future':
            method = 'fapiPrivateGetAllOrders'
        elif type == 'delivery':
            method = 'dapiPrivateGetAllOrders'
        elif type == 'margin':
            method = 'sapiGetMarginAllOrders'
        request = {
            'symbol': market['id'],
        }
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        query = self.omit(params, 'type')
        response = await getattr(self, method)(self.extend(request, query))
        #
        #  spot
        #
        #     [
        #         {
        #             "symbol": "LTCBTC",
        #             "orderId": 1,
        #             "clientOrderId": "myOrder1",
        #             "price": "0.1",
        #             "origQty": "1.0",
        #             "executedQty": "0.0",
        #             "cummulativeQuoteQty": "0.0",
        #             "status": "NEW",
        #             "timeInForce": "GTC",
        #             "type": "LIMIT",
        #             "side": "BUY",
        #             "stopPrice": "0.0",
        #             "icebergQty": "0.0",
        #             "time": 1499827319559,
        #             "updateTime": 1499827319559,
        #             "isWorking": True
        #         }
        #     ]
        #
        #  futures
        #
        #     [
        #         {
        #             "symbol": "BTCUSDT",
        #             "orderId": 1,
        #             "clientOrderId": "myOrder1",
        #             "price": "0.1",
        #             "origQty": "1.0",
        #             "executedQty": "1.0",
        #             "cumQuote": "10.0",
        #             "status": "NEW",
        #             "timeInForce": "GTC",
        #             "type": "LIMIT",
        #             "side": "BUY",
        #             "stopPrice": "0.0",
        #             "updateTime": 1499827319559
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        query = None
        type = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
            defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', market['type'])
            type = self.safe_string(params, 'type', defaultType)
            query = self.omit(params, 'type')
        elif self.options['warnOnFetchOpenOrdersWithoutSymbol']:
            symbols = self.symbols
            numSymbols = len(symbols)
            fetchOpenOrdersRateLimit = int(numSymbols / 2)
            raise ExchangeError(self.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + str(fetchOpenOrdersRateLimit) + ' seconds. Do not call self method frequently to avoid ban. Set ' + self.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = False to suppress self warning message.')
        else:
            defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', 'spot')
            type = self.safe_string(params, 'type', defaultType)
            query = self.omit(params, 'type')
        method = 'privateGetOpenOrders'
        if type == 'future':
            method = 'fapiPrivateGetOpenOrders'
        elif type == 'delivery':
            method = 'dapiPrivateGetOpenOrders'
        elif type == 'margin':
            method = 'sapiGetMarginOpenOrders'
        response = await getattr(self, method)(self.extend(request, query))
        return self.parse_orders(response, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        orders = await self.fetch_orders(symbol, since, limit, params)
        return self.filter_by(orders, 'status', 'closed')

    async def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', market['type'])
        type = self.safe_string(params, 'type', defaultType)
        # https://github.com/ccxt/ccxt/issues/6507
        origClientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
        request = {
            'symbol': market['id'],
            # 'orderId': id,
            # 'origClientOrderId': id,
        }
        if origClientOrderId is None:
            request['orderId'] = id
        else:
            request['origClientOrderId'] = origClientOrderId
        method = 'privateDeleteOrder'
        if type == 'future':
            method = 'fapiPrivateDeleteOrder'
        elif type == 'delivery':
            method = 'dapiPrivateDeleteOrder'
        elif type == 'margin':
            method = 'sapiDeleteMarginOrder'
        query = self.omit(params, ['type', 'origClientOrderId', 'clientOrderId'])
        response = await getattr(self, method)(self.extend(request, query))
        return self.parse_order(response)

    async def cancel_all_orders(self, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        defaultType = self.safe_string_2(self.options, 'cancelAllOrders', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        method = 'privateDeleteOpenOrders'
        if type == 'margin':
            method = 'sapiDeleteMarginOpenOrders'
        elif type == 'future':
            method = 'fapiPrivateDeleteAllOpenOrders'
        elif type == 'delivery':
            method = 'dapiPrivateDeleteAllOpenOrders'
        response = await getattr(self, method)(self.extend(request, query))
        if isinstance(response, list):
            return self.parse_orders(response, market)
        else:
            return response

    async def fetch_positions(self, symbols=None, since=None, limit=None, params={}):
        await self.load_markets()
        response = await self.fetch_balance(params)
        info = self.safe_value(response, 'info', {})
        #
        # futures, delivery
        #
        #     {
        #         "feeTier":0,
        #         "canTrade":true,
        #         "canDeposit":true,
        #         "canWithdraw":true,
        #         "updateTime":0,
        #         "assets":[
        #             {
        #                 "asset":"ETH",
        #                 "walletBalance":"0.09886711",
        #                 "unrealizedProfit":"0.00000000",
        #                 "marginBalance":"0.09886711",
        #                 "maintMargin":"0.00000000",
        #                 "initialMargin":"0.00000000",
        #                 "positionInitialMargin":"0.00000000",
        #                 "openOrderInitialMargin":"0.00000000",
        #                 "maxWithdrawAmount":"0.09886711",
        #                 "crossWalletBalance":"0.09886711",
        #                 "crossUnPnl":"0.00000000",
        #                 "availableBalance":"0.09886711"
        #             }
        #         ],
        #         "positions":[
        #             {
        #                 "symbol":"BTCUSD_201225",
        #                 "initialMargin":"0",
        #                 "maintMargin":"0",
        #                 "unrealizedProfit":"0.00000000",
        #                 "positionInitialMargin":"0",
        #                 "openOrderInitialMargin":"0",
        #                 "leverage":"20",
        #                 "isolated":false,
        #                 "positionSide":"BOTH",
        #                 "entryPrice":"0.00000000",
        #                 "maxQty":"250",  # "maxNotional" on futures
        #             },
        #         ]
        #     }
        #
        positions = self.safe_value_2(info, 'positions', 'userAssets', [])
        # todo unify parsePosition/parsePositions
        return positions

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        defaultType = self.safe_string_2(self.options, 'fetchMyTrades', 'defaultType', market['type'])
        type = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        method = None
        if type == 'spot':
            method = 'privateGetMyTrades'
        elif type == 'future':
            method = 'fapiPrivateGetUserTrades'
        elif type == 'delivery':
            method = 'dapiPrivateGetUserTrades'
        request = {
            'symbol': market['id'],
        }
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        response = await getattr(self, method)(self.extend(request, params))
        #
        # spot trade
        #
        #     [
        #         {
        #             "symbol": "BNBBTC",
        #             "id": 28457,
        #             "orderId": 100234,
        #             "price": "4.00000100",
        #             "qty": "12.00000000",
        #             "commission": "10.10000000",
        #             "commissionAsset": "BNB",
        #             "time": 1499865549590,
        #             "isBuyer": True,
        #             "isMaker": False,
        #             "isBestMatch": True,
        #         }
        #     ]
        #
        # futures trade
        #
        #     [
        #         {
        #             "accountId": 20,
        #             "buyer": False,
        #             "commission": "-0.07819010",
        #             "commissionAsset": "USDT",
        #             "counterPartyId": 653,
        #             "id": 698759,
        #             "maker": False,
        #             "orderId": 25851813,
        #             "price": "7819.01",
        #             "qty": "0.002",
        #             "quoteQty": "0.01563",
        #             "realizedPnl": "-0.91539999",
        #             "side": "SELL",
        #             "symbol": "BTCUSDT",
        #             "time": 1569514978020
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_my_dust_trades(self, symbol=None, since=None, limit=None, params={}):
        #
        # Binance provides an opportunity to trade insignificant(i.e. non-tradable and non-withdrawable)
        # token leftovers(of any asset) into `BNB` coin which in turn can be used to pay trading fees with it.
        # The corresponding trades history is called the `Dust Log` and can be requested via the following end-point:
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data
        #
        await self.load_markets()
        response = await self.wapiGetUserAssetDribbletLog(params)
        # {success:    True,
        #   results: {total:    1,
        #               rows: [{    transfered_total: "1.06468458",
        #                         service_charge_total: "0.02172826",
        #                                      tran_id: 2701371634,
        #                                         logs: [{             tranId:  2701371634,
        #                                                   serviceChargeAmount: "0.00012819",
        #                                                                   uid: "35103861",
        #                                                                amount: "0.8012",
        #                                                           operateTime: "2018-10-07 17:56:07",
        #                                                      transferedAmount: "0.00628141",
        #                                                             fromAsset: "ADA"                  }],
        #                                 operate_time: "2018-10-07 17:56:06"                                }]}}
        results = self.safe_value(response, 'results', {})
        rows = self.safe_value(results, 'rows', [])
        data = []
        for i in range(0, len(rows)):
            logs = rows[i]['logs']
            for j in range(0, len(logs)):
                logs[j]['isDustTrade'] = True
                data.append(logs[j])
        trades = self.parse_trades(data, None, since, limit)
        return self.filter_by_since_limit(trades, since, limit)

    def parse_dust_trade(self, trade, market=None):
        # {             tranId:  2701371634,
        #   serviceChargeAmount: "0.00012819",
        #                   uid: "35103861",
        #                amount: "0.8012",
        #           operateTime: "2018-10-07 17:56:07",
        #      transferedAmount: "0.00628141",
        #             fromAsset: "ADA"                  },
        orderId = self.safe_string(trade, 'tranId')
        timestamp = self.parse8601(self.safe_string(trade, 'operateTime'))
        tradedCurrency = self.safe_currency_code(self.safe_string(trade, 'fromAsset'))
        earnedCurrency = self.currency('BNB')['code']
        applicantSymbol = earnedCurrency + '/' + tradedCurrency
        tradedCurrencyIsQuote = False
        if applicantSymbol in self.markets:
            tradedCurrencyIsQuote = True
        #
        # Warning
        # Binance dust trade `fee` is already excluded from the `BNB` earning reported in the `Dust Log`.
        # So the parser should either set the `fee.cost` to `0` or add it on top of the earned
        # BNB `amount`(or `cost` depending on the trade `side`). The second of the above options
        # is much more illustrative and therefore preferable.
        #
        fee = {
            'currency': earnedCurrency,
            'cost': self.safe_float(trade, 'serviceChargeAmount'),
        }
        symbol = None
        amount = None
        cost = None
        side = None
        if tradedCurrencyIsQuote:
            symbol = applicantSymbol
            amount = self.sum(self.safe_float(trade, 'transferedAmount'), fee['cost'])
            cost = self.safe_float(trade, 'amount')
            side = 'buy'
        else:
            symbol = tradedCurrency + '/' + earnedCurrency
            amount = self.safe_float(trade, 'amount')
            cost = self.sum(self.safe_float(trade, 'transferedAmount'), fee['cost'])
            side = 'sell'
        price = None
        if cost is not None:
            if amount:
                price = cost / amount
        id = None
        type = None
        takerOrMaker = None
        return {
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'amount': amount,
            'price': price,
            'cost': cost,
            'fee': fee,
            'info': trade,
        }

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        currency = None
        request = {}
        if code is not None:
            currency = self.currency(code)
            request['asset'] = currency['id']
        if since is not None:
            request['startTime'] = since
            # max 3 months range https://github.com/ccxt/ccxt/issues/6495
            request['endTime'] = self.sum(since, 7776000000)
        response = await self.wapiGetDepositHistory(self.extend(request, params))
        #
        #     {    success:    True,
        #       depositList: [{insertTime:  1517425007000,
        #                            amount:  0.3,
        #                           address: "0x0123456789abcdef",
        #                        addressTag: "",
        #                              txId: "0x0123456789abcdef",
        #                             asset: "ETH",
        #                            status:  1                                                                    }]}
        #
        return self.parse_transactions(response['depositList'], currency, since, limit)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        currency = None
        request = {}
        if code is not None:
            currency = self.currency(code)
            request['asset'] = currency['id']
        if since is not None:
            request['startTime'] = since
            # max 3 months range https://github.com/ccxt/ccxt/issues/6495
            request['endTime'] = self.sum(since, 7776000000)
        response = await self.wapiGetWithdrawHistory(self.extend(request, params))
        #
        #     {withdrawList: [{     amount:  14,
        #                             address: "0x0123456789abcdef...",
        #                         successTime:  1514489710000,
        #                      transactionFee:  0.01,
        #                          addressTag: "",
        #                                txId: "0x0123456789abcdef...",
        #                                  id: "0123456789abcdef...",
        #                               asset: "ETH",
        #                           applyTime:  1514488724000,
        #                              status:  6                       },
        #                       {     amount:  7600,
        #                             address: "0x0123456789abcdef...",
        #                         successTime:  1515323226000,
        #                      transactionFee:  0.01,
        #                          addressTag: "",
        #                                txId: "0x0123456789abcdef...",
        #                                  id: "0123456789abcdef...",
        #                               asset: "ICN",
        #                           applyTime:  1515322539000,
        #                              status:  6                       }  ],
        #            success:    True                                         }
        #
        return self.parse_transactions(response['withdrawList'], currency, since, limit)

    def parse_transaction_status_by_type(self, status, type=None):
        statusesByType = {
            'deposit': {
                '0': 'pending',
                '1': 'ok',
            },
            'withdrawal': {
                '0': 'pending',  # Email Sent
                '1': 'canceled',  # Cancelled(different from 1 = ok in deposits)
                '2': 'pending',  # Awaiting Approval
                '3': 'failed',  # Rejected
                '4': 'pending',  # Processing
                '5': 'failed',  # Failure
                '6': 'ok',  # Completed
            },
        }
        statuses = self.safe_value(statusesByType, type, {})
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #         insertTime:  1517425007000,
        #         amount:  0.3,
        #         address: "0x0123456789abcdef",
        #         addressTag: "",
        #         txId: "0x0123456789abcdef",
        #         asset: "ETH",
        #         status:  1
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         amount:  14,
        #         address: "0x0123456789abcdef...",
        #         successTime:  1514489710000,
        #         transactionFee:  0.01,
        #         addressTag: "",
        #         txId: "0x0123456789abcdef...",
        #         id: "0123456789abcdef...",
        #         asset: "ETH",
        #         applyTime:  1514488724000,
        #         status:  6
        #     }
        #
        id = self.safe_string(transaction, 'id')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'addressTag')  # set but unused
        if tag is not None:
            if len(tag) < 1:
                tag = None
        txid = self.safe_string(transaction, 'txId')
        if (txid is not None) and (txid.find('Internal transfer ') >= 0):
            txid = txid[18:]
        currencyId = self.safe_string(transaction, 'asset')
        code = self.safe_currency_code(currencyId, currency)
        timestamp = None
        insertTime = self.safe_integer(transaction, 'insertTime')
        applyTime = self.safe_integer(transaction, 'applyTime')
        type = self.safe_string(transaction, 'type')
        if type is None:
            if (insertTime is not None) and (applyTime is None):
                type = 'deposit'
                timestamp = insertTime
            elif (insertTime is None) and (applyTime is not None):
                type = 'withdrawal'
                timestamp = applyTime
        status = self.parse_transaction_status_by_type(self.safe_string(transaction, 'status'), type)
        amount = self.safe_float(transaction, 'amount')
        feeCost = self.safe_float(transaction, 'transactionFee')
        fee = None
        if feeCost is not None:
            fee = {'currency': code, 'cost': feeCost}
        updated = self.safe_integer(transaction, 'successTime')
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'addressTo': address,
            'addressFrom': None,
            'tag': tag,
            'tagTo': tag,
            'tagFrom': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
            # 'network': 'ETH',  # 'BSC', 'XMR', you can get network and isDefault in networkList in the response of sapiGetCapitalConfigDetail
        }
        # has support for the 'network' parameter
        # https://binance-docs.github.io/apidocs/spot/en/#deposit-address-supporting-network-user_data
        response = await self.sapiGetCapitalDepositAddress(self.extend(request, params))
        #
        #     {
        #         currency: 'XRP',
        #         address: 'rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh',
        #         tag: '108618262',
        #         info: {
        #             coin: 'XRP',
        #             address: 'rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh',
        #             tag: '108618262',
        #             url: 'https://bithomp.com/explorer/rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh'
        #         }
        #     }
        #
        address = self.safe_string(response, 'address')
        tag = self.safe_string(response, 'tag')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': response,
        }

    async def fetch_funding_fees(self, codes=None, params={}):
        response = await self.wapiGetAssetDetail(params)
        #
        #     {
        #         "success": True,
        #         "assetDetail": {
        #             "CTR": {
        #                 "minWithdrawAmount": "70.00000000",  #min withdraw amount
        #                 "depositStatus": False,//deposit status
        #                 "withdrawFee": 35,  # withdraw fee
        #                 "withdrawStatus": True,  #withdraw status
        #                 "depositTip": "Delisted, Deposit Suspended"  #reason
        #             },
        #             "SKY": {
        #                 "minWithdrawAmount": "0.02000000",
        #                 "depositStatus": True,
        #                 "withdrawFee": 0.01,
        #                 "withdrawStatus": True
        #             }
        #         }
        #     }
        #
        detail = self.safe_value(response, 'assetDetail', {})
        ids = list(detail.keys())
        withdrawFees = {}
        for i in range(0, len(ids)):
            id = ids[i]
            code = self.safe_currency_code(id)
            withdrawFees[code] = self.safe_float(detail[id], 'withdrawFee')
        return {
            'withdraw': withdrawFees,
            'deposit': {},
            'info': response,
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        # name is optional, can be overrided via params
        name = address[0:20]
        request = {
            'asset': currency['id'],
            'address': address,
            'amount': float(amount),
            'name': name,  # name is optional, can be overrided via params
            # https://binance-docs.github.io/apidocs/spot/en/#withdraw-sapi
            # issue sapiGetCapitalConfigGetall() to get networks for withdrawing USDT ERC20 vs USDT Omni
            # 'network': 'ETH',  # 'BTC', 'TRX', etc, optional
        }
        if tag is not None:
            request['addressTag'] = tag
        response = await self.wapiPostWithdraw(self.extend(request, params))
        return {
            'info': response,
            'id': self.safe_string(response, 'id'),
        }

    def parse_trading_fee(self, fee, market=None):
        #
        #     {
        #         "symbol": "ADABNB",
        #         "maker": 0.9000,
        #         "taker": 1.0000
        #     }
        #
        marketId = self.safe_string(fee, 'symbol')
        symbol = self.safe_symbol(marketId)
        return {
            'info': fee,
            'symbol': symbol,
            'maker': self.safe_float(fee, 'maker'),
            'taker': self.safe_float(fee, 'taker'),
        }

    async def fetch_trading_fee(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = await self.wapiGetTradeFee(self.extend(request, params))
        #
        #     {
        #         "tradeFee": [
        #             {
        #                 "symbol": "ADABNB",
        #                 "maker": 0.9000,
        #                 "taker": 1.0000
        #             }
        #         ],
        #         "success": True
        #     }
        #
        tradeFee = self.safe_value(response, 'tradeFee', [])
        first = self.safe_value(tradeFee, 0, {})
        return self.parse_trading_fee(first)

    async def fetch_trading_fees(self, params={}):
        await self.load_markets()
        response = await self.wapiGetTradeFee(params)
        #
        #     {
        #         "tradeFee": [
        #             {
        #                 "symbol": "ADABNB",
        #                 "maker": 0.9000,
        #                 "taker": 1.0000
        #             }
        #         ],
        #         "success": True
        #     }
        #
        tradeFee = self.safe_value(response, 'tradeFee', [])
        result = {}
        for i in range(0, len(tradeFee)):
            fee = self.parse_trading_fee(tradeFee[i])
            symbol = fee['symbol']
            result[symbol] = fee
        return result

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        if not (api in self.urls['api']):
            raise NotSupported(self.id + ' does not have a testnet/sandbox URL for ' + api + ' endpoints')
        url = self.urls['api'][api]
        url += '/' + path
        if api == 'wapi':
            url += '.html'
        userDataStream = (path == 'userDataStream') or (path == 'listenKey')
        if path == 'historicalTrades':
            if self.apiKey:
                headers = {
                    'X-MBX-APIKEY': self.apiKey,
                }
            else:
                raise AuthenticationError(self.id + ' historicalTrades endpoint requires `apiKey` credential')
        elif userDataStream:
            if self.apiKey:
                # v1 special case for userDataStream
                body = self.urlencode(params)
                headers = {
                    'X-MBX-APIKEY': self.apiKey,
                    'Content-Type': 'application/x-www-form-urlencoded',
                }
            else:
                raise AuthenticationError(self.id + ' userDataStream endpoint requires `apiKey` credential')
        if (api == 'private') or (api == 'sapi') or (api == 'wapi' and path != 'systemStatus') or (api == 'dapiPrivate') or (api == 'fapiPrivate') or (api == 'fapiPrivateV2'):
            self.check_required_credentials()
            query = None
            recvWindow = self.safe_integer(self.options, 'recvWindow', 5000)
            if (api == 'sapi') and (path == 'asset/dust'):
                query = self.urlencode_with_array_repeat(self.extend({
                    'timestamp': self.nonce(),
                    'recvWindow': recvWindow,
                }, params))
            elif (path == 'batchOrders') or (path.find('sub-account') >= 0):
                query = self.rawencode(self.extend({
                    'timestamp': self.nonce(),
                    'recvWindow': recvWindow,
                }, params))
            else:
                query = self.urlencode(self.extend({
                    'timestamp': self.nonce(),
                    'recvWindow': recvWindow,
                }, params))
            signature = self.hmac(self.encode(query), self.encode(self.secret))
            query += '&' + 'signature=' + signature
            headers = {
                'X-MBX-APIKEY': self.apiKey,
            }
            if (method == 'GET') or (method == 'DELETE') or (api == 'wapi'):
                url += '?' + query
            else:
                body = query
                headers['Content-Type'] = 'application/x-www-form-urlencoded'
        else:
            # userDataStream endpoints are public, but POST, PUT, DELETE
            # therefore they don't accept URL query arguments
            # https://github.com/ccxt/ccxt/issues/5224
            if not userDataStream:
                if params:
                    url += '?' + self.urlencode(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if (code == 418) or (code == 429):
            raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
        # error response in a form: {"code": -1013, "msg": "Invalid quantity."}
        # following block cointains legacy checks against message patterns in "msg" property
        # will switch "code" checks eventually, when we know all of them
        if code >= 400:
            if body.find('Price * QTY is zero or less') >= 0:
                raise InvalidOrder(self.id + ' order cost = amount * price is zero or less ' + body)
            if body.find('LOT_SIZE') >= 0:
                raise InvalidOrder(self.id + ' order amount should be evenly divisible by lot size ' + body)
            if body.find('PRICE_FILTER') >= 0:
                raise InvalidOrder(self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use self.price_to_precision(symbol, amount) ' + body)
        if response is None:
            return  # fallback to default error handler
        # check success value for wapi endpoints
        # response in format {'msg': 'The coin does not exist.', 'success': True/false}
        success = self.safe_value(response, 'success', True)
        if not success:
            message = self.safe_string(response, 'msg')
            parsedMessage = None
            if message is not None:
                try:
                    parsedMessage = json.loads(message)
                except Exception as e:
                    # do nothing
                    parsedMessage = None
                if parsedMessage is not None:
                    response = parsedMessage
        message = self.safe_string(response, 'msg')
        if message is not None:
            self.throw_exactly_matched_exception(self.exceptions, message, self.id + ' ' + message)
        # checks against error codes
        error = self.safe_string(response, 'code')
        if error is not None:
            # https://github.com/ccxt/ccxt/issues/6501
            # https://github.com/ccxt/ccxt/issues/7742
            if (error == '200') or (error == '0'):
                return
            # a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
            # despite that their message is very confusing, it is raised by Binance
            # on a temporary ban, the API key is valid, but disabled for a while
            if (error == '-2015') and self.options['hasAlreadyAuthenticatedSuccessfully']:
                raise DDoSProtection(self.id + ' temporary banned: ' + body)
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions, error, feedback)
            raise ExchangeError(feedback)
        if not success:
            raise ExchangeError(self.id + ' ' + body)

    async def request(self, path, api='public', method='GET', params={}, headers=None, body=None):
        response = await self.fetch2(path, api, method, params, headers, body)
        # a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
        if (api == 'private') or (api == 'wapi'):
            self.options['hasAlreadyAuthenticatedSuccessfully'] = True
        return response
