# -*- 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
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
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 DDoSProtection
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class currencycom(Exchange):

    def describe(self):
        return self.deep_extend(super(currencycom, self).describe(), {
            'id': 'currencycom',
            'name': 'Currency.com',
            'countries': ['BY'],  # Belarus
            'rateLimit': 100,
            'certified': True,
            'pro': True,
            'version': 'v2',
            # new metainfo interface
            'has': {
                'CORS': None,
                'spot': True,
                'margin': True,
                'swap': True,
                'future': False,
                'option': False,
                'addMargin': None,
                'cancelAllOrders': None,
                'cancelOrder': True,
                'cancelOrders': None,
                'createDepositAddress': None,
                'createLimitOrder': True,
                'createMarketOrder': True,
                'createOrder': True,
                'deposit': None,
                'editOrder': 'emulated',
                'fetchAccounts': True,
                'fetchBalance': True,
                'fetchBidsAsks': None,
                'fetchBorrowRate': None,
                'fetchBorrowRateHistory': None,
                'fetchBorrowRates': None,
                'fetchBorrowRatesPerSymbol': None,
                'fetchCanceledOrders': None,
                'fetchClosedOrder': None,
                'fetchClosedOrders': None,
                'fetchCurrencies': True,
                'fetchDeposit': None,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': True,
                'fetchFundingFee': None,
                'fetchFundingFees': None,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchL2OrderBook': True,
                'fetchLedger': True,
                'fetchLedgerEntry': False,
                'fetchLeverage': True,
                'fetchLeverageTiers': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrder': None,
                'fetchOpenOrders': True,
                'fetchOrder': None,
                'fetchOrderBook': True,
                'fetchOrderBooks': None,
                'fetchOrders': None,
                'fetchOrderTrades': None,
                'fetchPosition': None,
                'fetchPositions': True,
                'fetchPositionsRisk': None,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTradingLimits': None,
                'fetchTransactions': True,
                'fetchTransfers': None,
                'fetchWithdrawal': None,
                'fetchWithdrawals': True,
                'reduceMargin': None,
                'setLeverage': None,
                'setMarginMode': None,
                'setPositionMode': None,
                'signIn': None,
                'transfer': None,
                'withdraw': None,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '10m': '10m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '1d': '1d',
                '1w': '1w',
            },
            'hostname': 'backend.currency.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/83718672-36745c00-a63e-11ea-81a9-677b1f789a4d.jpg',
                'api': {
                    'public': 'https://api-adapter.{hostname}/api',
                    'private': 'https://api-adapter.{hostname}/api',
                    'marketcap': 'https://marketcap.{hostname}/api',
                },
                'test': {
                    'public': 'https://demo-api-adapter.{hostname}/api',
                    'private': 'https://demo-api-adapter.{hostname}/api',
                },
                'www': 'https://www.currency.com',
                'referral': 'https://currency.com/trading/signup?c=362jaimv&pid=referral',
                'doc': [
                    'https://currency.com/api',
                ],
                'fees': 'https://currency.com/fees-charges',
            },
            # rate-limits are described at: https://currency.com/api-get-started
            'api': {
                'public': {
                    'get': {
                        'v1/time': 1,
                        'v1/exchangeInfo': 1,
                        'v1/depth': 1,
                        'v1/aggTrades': 1,
                        'v1/klines': 1,
                        'v1/ticker/24hr': 1,
                        'v2/time': 1,
                        'v2/exchangeInfo': 1,
                        'v2/depth': 1,
                        'v2/aggTrades': 1,
                        'v2/klines': 1,
                        'v2/ticker/24hr': 1,
                    },
                },
                'marketcap': {
                    'get': {
                        'v1/assets': 1,
                        'v1/candles': 1,
                        'v1/orderbook': 1,
                        'v1/summary': 1,
                        'v1/ticker': 1,
                        'v1/token/assets': 1,
                        'v1/token/orderbook': 1,
                        'v1/token/summary': 1,
                        'v1/token/ticker': 1,
                        'v1/token/trades': 1,
                        'v1/token_crypto/OHLC': 1,
                        'v1/token_crypto/assets': 1,
                        'v1/token_crypto/orderbook': 1,
                        'v1/token_crypto/summary': 1,
                        'v1/token_crypto/ticker': 1,
                        'v1/token_crypto/trades': 1,
                        'v1/trades': 1,
                    },
                },
                'private': {
                    'get': {
                        'v1/account': 1,
                        'v1/currencies': 1,
                        'v1/deposits': 1,
                        'v1/depositAddress': 1,
                        'v1/ledger': 1,
                        'v1/leverageSettings': 1,
                        'v1/myTrades': 1,
                        'v1/openOrders': 1,
                        'v1/tradingPositions': 1,
                        'v1/tradingPositionsHistory': 1,
                        'v1/transactions': 1,
                        'v1/withdrawals': 1,
                        'v2/account': 1,
                        'v2/currencies': 1,
                        'v2/deposits': 1,
                        'v2/depositAddress': 1,
                        'v2/ledger': 1,
                        'v2/leverageSettings': 1,
                        'v2/myTrades': 1,
                        'v2/openOrders': 1,
                        'v2/tradingPositions': 1,
                        'v2/tradingPositionsHistory': 1,
                        'v2/transactions': 1,
                        'v2/withdrawals': 1,
                    },
                    'post': {
                        'v1/order': 1,
                        'v1/updateTradingPosition': 1,
                        'v1/updateTradingOrder': 1,
                        'v1/closeTradingPosition': 1,
                        'v2/order': 1,
                        'v2/updateTradingPosition': 1,
                        'v2/updateTradingOrder': 1,
                        'v2/closeTradingPosition': 1,
                    },
                    'delete': {
                        'v1/order': 1,
                        'v2/order': 1,
                    },
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': False,
                    'percentage': True,
                    'taker': self.parse_number('0.002'),
                    'maker': self.parse_number('0.002'),
                },
            },
            'precisionMode': TICK_SIZE,
            # exchange-specific options
            'options': {
                'defaultTimeInForce': 'GTC',  # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel, 'FOK' = Fill Or Kill
                'warnOnFetchOpenOrdersWithoutSymbol': True,
                'recvWindow': 5 * 1000,  # 5 sec, 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'
                    'stop': 'RESULT',
                },
            },
            'exceptions': {
                'broad': {
                    'FIELD_VALIDATION_ERROR Cancel is available only for LIMIT order': InvalidOrder,
                    'API key does not exist': AuthenticationError,
                    'Order would trigger immediately.': InvalidOrder,
                    'Account has insufficient balance for requested action.': InsufficientFunds,
                    'Rest API trading is not enabled.': ExchangeNotAvailable,
                    'Combination of parameters invalid': BadRequest,
                    'Invalid limit price': BadRequest,
                    'Only leverage symbol allowed here:': BadSymbol,  # when you fetchLeverage for non-leverage symbols, like 'BTC/USDT' instead of 'BTC/USDT_LEVERAGE': {"code":"-1128","msg":"Only leverage symbol allowed here: BTC/USDT"}
                },
                'exact': {
                    '-1000': ExchangeNotAvailable,  # {"code":-1000,"msg":"An unknown error occured while processing the request."}
                    '-1013': InvalidOrder,  # createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL
                    '-1021': InvalidNonce,  # 'your time is ahead of server'
                    '-1022': AuthenticationError,  # {"code":-1022,"msg":"Signature for self request is not valid."}
                    '-1100': InvalidOrder,  # createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'
                    '-1104': ExchangeError,  # Not all sent parameters were read, read 8 parameters but was sent 9
                    '-1025': AuthenticationError,  # {"code":-1025,"msg":"Invalid API-key, IP, or permissions for action"}
                    '-1128': BadRequest,  # {"code":-1128,"msg":"Combination of optional parameters invalid."} | {"code":"-1128","msg":"Combination of parameters invalid"} | {"code":"-1128","msg":"Invalid limit price"}
                    '-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."
                },
            },
            'commonCurrencies': {
                'ACN': 'Accenture',
                'BNS': 'Bank of Nova Scotia',
                'CAR': 'Avis Budget Group Inc',
                'CLR': 'Continental Resources',
                'EDU': 'New Oriental Education & Technology Group Inc',
                'ETN': 'Eaton',
                'FOX': 'Fox Corporation',
                'GM': 'General Motors Co',
                'IQ': 'iQIYI',
                'OSK': 'Oshkosh',
                'PLAY': "Dave & Buster's Entertainment",
            },
        })

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

    async def fetch_time(self, params={}):
        response = await self.publicGetV2Time(params)
        #
        #     {
        #         "serverTime": 1590998366609
        #     }
        #
        return self.safe_integer(response, 'serverTime')

    async def fetch_currencies(self, params={}):
        # requires authentication
        if not self.check_required_credentials(False):
            return None
        response = await self.privateGetV2Currencies(params)
        #
        #     [
        #         {
        #             "name": "Euro",
        #             "displaySymbol": "EUR.cx",
        #             "precision": "2",
        #             "type": "FIAT",
        #             "minWithdrawal": "90.0",
        #             "maxWithdrawal": "1.0E+8",
        #             "commissionMin": "0.02",  # some instruments do not have self property
        #             "commissionPercent": "1.5",  # some instruments do not have self property
        #             "minDeposit": "90.0",
        #         },
        #         {
        #             name: "Bitcoin",
        #             displaySymbol: "BTC",
        #             precision: "8",
        #             type: "CRYPTO",  # only a few major currencies have self value, others like USDT have a value of "TOKEN"
        #             minWithdrawal: "0.00020",
        #             commissionFixed: "0.00010",
        #             minDeposit: "0.00010",
        #         },
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'displaySymbol')
            code = self.safe_currency_code(id)
            fee = self.safe_number(currency, 'commissionFixed')
            precision = self.safe_integer(currency, 'precision')
            result[code] = {
                'id': id,
                'code': code,
                'address': self.safe_string(currency, 'baseAddress'),
                'type': self.safe_string_lower(currency, 'type'),
                'name': self.safe_string(currency, 'name'),
                'active': None,
                'deposit': None,
                'withdraw': None,
                'fee': fee,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_number(currency, 'minWithdrawal'),
                        'max': self.safe_number(currency, 'maxWithdrawal'),
                    },
                    'deposit': {
                        'min': self.safe_number(currency, 'minDeposit'),
                        'max': None,
                    },
                },
                'info': currency,
            }
        return result

    async def fetch_markets(self, params={}):
        response = await self.publicGetV2ExchangeInfo(params)
        #
        #     {
        #         "timezone":"UTC",
        #         "serverTime":1603252990096,
        #         "rateLimits":[
        #             {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},
        #             {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":1,"limit":10},
        #             {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":864000},
        #         ],
        #         "exchangeFilters":[],
        #         "symbols":[
        #             {
        #                 "symbol":"EVK",
        #                 "name":"Evonik",
        #                 "status":"BREAK",
        #                 "baseAsset":"EVK",
        #                 "baseAssetPrecision":3,
        #                 "quoteAsset":"EUR",
        #                 "quoteAssetId":"EUR",
        #                 "quotePrecision":3,
        #                 "orderTypes":["LIMIT","MARKET"],
        #                 "filters":[
        #                     {"filterType":"LOT_SIZE","minQty":"1","maxQty":"27000","stepSize":"1"},
        #                     {"filterType":"MIN_NOTIONAL","minNotional":"23"}
        #                 ],
        #                 "marketType":"SPOT",
        #                 "country":"DE",
        #                 "sector":"Basic Materials",
        #                 "industry":"Diversified Chemicals",
        #                 "tradingHours":"UTC; Mon 07:02 - 15:30; Tue 07:02 - 15:30; Wed 07:02 - 15:30; Thu 07:02 - 15:30; Fri 07:02 - 15:30",
        #                 "tickSize":0.005,
        #                 "tickValue":0.11125,
        #                 "exchangeFee":0.05
        #             },
        #             {
        #                 "symbol":"BTC/USD_LEVERAGE",
        #                 "name":"Bitcoin / USD",
        #                 "status":"TRADING",
        #                 "baseAsset":"BTC",
        #                 "baseAssetPrecision":3,
        #                 "quoteAsset":"USD",
        #                 "quoteAssetId":"USD_LEVERAGE",
        #                 "quotePrecision":3,
        #                 "orderTypes":["LIMIT","MARKET","STOP"],
        #                 "filters":[
        #                     {"filterType":"LOT_SIZE","minQty":"0.001","maxQty":"100","stepSize":"0.001"},
        #                     {"filterType":"MIN_NOTIONAL","minNotional":"13"}
        #                 ],
        #                 "marketType":"LEVERAGE",
        #                 "longRate":-0.01,
        #                 "shortRate":0.01,
        #                 "swapChargeInterval":480,
        #                 "country":"",
        #                 "sector":"",
        #                 "industry":"",
        #                 "tradingHours":"UTC; Mon - 21:00, 21:05 -; Tue - 21:00, 21:05 -; Wed - 21:00, 21:05 -; Thu - 21:00, 21:05 -; Fri - 21:00, 22:01 -; Sat - 21:00, 21:05 -; Sun - 20:00, 21:05 -",
        #                 "tickSize":0.05,
        #                 "tickValue":610.20875,
        #                 "makerFee":-0.025,
        #                 "takerFee":0.075
        #             },
        #         ]
        #     }
        #
        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]
            id = self.safe_string(market, 'symbol')
            baseId = self.safe_string(market, 'baseAsset')
            quoteId = self.safe_string(market, 'quoteAsset')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            if id.find('/') >= 0:
                symbol = id
            filters = self.safe_value(market, 'filters', [])
            filtersByType = self.index_by(filters, 'filterType')
            status = self.safe_string(market, 'status')
            active = (status == 'TRADING')
            type = self.safe_string_lower(market, 'marketType')
            if type == 'leverage':
                type = 'margin'
            spot = (type == 'spot')
            margin = (type == 'margin')
            exchangeFee = self.safe_string_2(market, 'exchangeFee', 'tradingFee')
            maker = Precise.string_div(self.safe_string(market, 'makerFee', exchangeFee), '100')
            taker = Precise.string_div(self.safe_string(market, 'takerFee', exchangeFee), '100')
            limitPriceMin = None
            limitPriceMax = None
            precisionPrice = self.safe_number(market, 'tickSize')
            if 'PRICE_FILTER' in filtersByType:
                filter = self.safe_value(filtersByType, 'PRICE_FILTER', {})
                precisionPrice = self.safe_number(filter, 'tickSize')
                # 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
                limitPriceMin = self.safe_number(filter, 'minPrice')
                maxPrice = self.safe_number(filter, 'maxPrice')
                if (maxPrice is not None) and (maxPrice > 0):
                    limitPriceMax = maxPrice
            precisionAmount = self.parse_number(self.parse_precision(self.safe_string(market, 'baseAssetPrecision')))
            limitAmount = {
                'min': None,
                'max': None,
            }
            if 'LOT_SIZE' in filtersByType:
                filter = self.safe_value(filtersByType, 'LOT_SIZE', {})
                precisionAmount = self.safe_number(filter, 'stepSize')
                limitAmount = {
                    'min': self.safe_number(filter, 'minQty'),
                    'max': self.safe_number(filter, 'maxQty'),
                }
            limitMarket = {
                'min': None,
                'max': None,
            }
            if 'MARKET_LOT_SIZE' in filtersByType:
                filter = self.safe_value(filtersByType, 'MARKET_LOT_SIZE', {})
                limitMarket = {
                    'min': self.safe_number(filter, 'minQty'),
                    'max': self.safe_number(filter, 'maxQty'),
                }
            costMin = None
            if 'MIN_NOTIONAL' in filtersByType:
                filter = self.safe_value(filtersByType, 'MIN_NOTIONAL', {})
                costMin = self.safe_number(filter, 'minNotional')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': type,
                'spot': spot,
                'margin': margin,
                'swap': False,
                'future': False,
                'option': False,
                'active': active,
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': self.parse_number(taker),
                'maker': self.parse_number(maker),
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': precisionAmount,
                    'price': precisionPrice,
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': limitAmount,
                    'market': limitMarket,
                    'price': {
                        'min': limitPriceMin,
                        'max': limitPriceMax,
                    },
                    'cost': {
                        'min': costMin,
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    async def fetch_accounts(self, params={}):
        response = await self.privateGetV2Account(params)
        #
        #     {
        #         "makerCommission": "0.20",
        #         "takerCommission": "0.20",
        #         "buyerCommission": "0.20",
        #         "sellerCommission": "0.20",
        #         "canTrade": True,
        #         "canWithdraw": True,
        #         "canDeposit": True,
        #         "updateTime": "1645266330",
        #         "userId": "644722",
        #         "balances": [
        #             {
        #                 "accountId": "120702016179403605",
        #                 "collateralCurrency": False,
        #                 "asset": "CAKE",
        #                 "free": "1.784",
        #                 "locked": "0.0",
        #                 "default": False,
        #             },
        #             {
        #                 "accountId": "109698017713125316",
        #                 "collateralCurrency": True,
        #                 "asset": "USD",
        #                 "free": "7.58632",
        #                 "locked": "0.0",
        #                 "default": True,
        #             }
        #         ]
        #     }
        #
        accounts = self.safe_value(response, 'balances', [])
        result = []
        for i in range(0, len(accounts)):
            account = accounts[i]
            accountId = self.safe_integer(account, 'accountId')
            currencyId = self.safe_string(account, 'asset')
            currencyCode = self.safe_currency_code(currencyId)
            result.append({
                'id': accountId,
                'type': None,
                'currency': currencyCode,
                'info': response,
            })
        return result

    async def fetch_trading_fees(self, params={}):
        await self.load_markets()
        response = await self.privateGetV2Account(params)
        #
        #    {
        #        makerCommission: '0.20',
        #        takerCommission: '0.20',
        #        buyerCommission: '0.20',
        #        sellerCommission: '0.20',
        #        canTrade: True,
        #        canWithdraw: True,
        #        canDeposit: True,
        #        updateTime: '1645738976',
        #        userId: '-1924114235',
        #        balances: []
        #    }
        #
        makerFee = self.safe_number(response, 'makerCommission')
        takerFee = self.safe_number(response, 'takerCommission')
        result = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            result[symbol] = {
                'info': response,
                'symbol': symbol,
                'maker': makerFee,
                'taker': takerFee,
                'percentage': True,
                'tierBased': False,
            }
        return result

    def parse_balance(self, response, type=None):
        #
        #     {
        #         "makerCommission":0.20,
        #         "takerCommission":0.20,
        #         "buyerCommission":0.20,
        #         "sellerCommission":0.20,
        #         "canTrade":true,
        #         "canWithdraw":true,
        #         "canDeposit":true,
        #         "updateTime":1591056268,
        #         "balances":[
        #             {
        #                 "accountId":5470306579272368,
        #                 "collateralCurrency":true,
        #                 "asset":"ETH",
        #                 "free":0.0,
        #                 "locked":0.0,
        #                 "default":false,
        #             },
        #         ]
        #     }
        #
        result = {'info': response}
        balances = self.safe_value(response, 'balances', [])
        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_string(balance, 'free')
            account['used'] = self.safe_string(balance, 'locked')
            result[code] = account
        return self.safe_balance(result)

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetV2Account(params)
        #
        #     {
        #         "makerCommission": "0.20",
        #         "takerCommission": "0.20",
        #         "buyerCommission": "0.20",
        #         "sellerCommission": "0.20",
        #         "canTrade": True,
        #         "canWithdraw": True,
        #         "canDeposit": True,
        #         "updateTime": "1645266330",
        #         "userId": "644722",
        #         "balances": [
        #             {
        #                 "accountId": "120702016179403605",
        #                 "collateralCurrency": False,
        #                 "asset": "CAKE",
        #                 "free": "1.784",
        #                 "locked": "0.0",
        #                 "default": False,
        #             },
        #             {
        #                 "accountId": "109698017413175316",
        #                 "collateralCurrency": True,
        #                 "asset": "USD",
        #                 "free": "7.58632",
        #                 "locked": "0.0",
        #                 "default": True,
        #             }
        #         ]
        #     }
        #
        return self.parse_balance(response)

    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 1000, valid limits 5, 10, 20, 50, 100, 500, 1000, 5000
        response = await self.publicGetV2Depth(self.extend(request, params))
        #
        #     {
        #         "lastUpdateId":1590999849037,
        #         "asks":[
        #             [0.02495,60.0345],
        #             [0.02496,34.1],
        #             ...
        #         ],
        #         "bids":[
        #             [0.02487,72.4144854],
        #             [0.02486,24.043],
        #             ...
        #         ]
        #     }
        #
        orderbook = self.parse_order_book(response, symbol)
        orderbook['nonce'] = self.safe_integer(response, 'lastUpdateId')
        return orderbook

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker
        #
        #     {
        #         "symbol":"ETH/BTC",
        #         "priceChange":"0.00030",
        #         "priceChangePercent":"1.21",
        #         "weightedAvgPrice":"0.02481",
        #         "prevClosePrice":"0.02447",
        #         "lastPrice":"0.02477",
        #         "lastQty":"60.0",
        #         "bidPrice":"0.02477",
        #         "askPrice":"0.02484",
        #         "openPrice":"0.02447",
        #         "highPrice":"0.02524",
        #         "lowPrice":"0.02438",
        #         "volume":"11.97",
        #         "quoteVolume":"0.298053",
        #         "openTime":1590969600000,
        #         "closeTime":1591000072693
        #     }
        #
        # fetchTickers
        #
        #     {
        #          "symbol": "SHIB/USD_LEVERAGE",
        #          "weightedAvgPrice": "0.000027595",
        #          "lastPrice": "0.00002737",
        #          "lastQty": "1.11111111E8",
        #          "bidPrice": "0.00002737",
        #          "askPrice": "0.00002782",
        #          "highPrice": "0.00002896",
        #          "lowPrice": "0.00002738",
        #          "volume": "16472160000",
        #          "quoteVolume": "454796.3376",
        #          "openTime": "1645187472000",
        #          "closeTime": "1645273872000",
        #     }
        #
        # ws:marketData.subscribe
        #
        #     {
        #          "symbolName":"TXN",
        #          "bid":139.85,
        #          "bidQty":2500,
        #          "ofr":139.92000000000002,
        #          "ofrQty":2500,
        #          "timestamp":1597850971558
        #      }
        #
        timestamp = self.safe_integer_2(ticker, 'closeTime', 'timestamp')
        marketId = self.safe_string_2(ticker, 'symbol', 'symbolName')
        market = self.safe_market(marketId, market, '/')
        last = self.safe_string(ticker, 'lastPrice')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'highPrice'),
            'low': self.safe_string(ticker, 'lowPrice'),
            'bid': self.safe_string_2(ticker, 'bidPrice', 'bid'),
            'bidVolume': self.safe_string(ticker, 'bidQty'),
            'ask': self.safe_string_2(ticker, 'askPrice', 'ofr'),
            'askVolume': self.safe_string(ticker, 'ofrQty'),
            'vwap': self.safe_string(ticker, 'weightedAvgPrice'),
            'open': self.safe_string(ticker, 'openPrice'),
            'close': last,
            'last': last,
            'previousClose': self.safe_string(ticker, 'prevClosePrice'),  # previous day close
            'change': self.safe_string(ticker, 'priceChange'),
            'percentage': self.safe_string(ticker, 'priceChangePercent'),
            'average': None,
            'baseVolume': self.safe_string(ticker, 'volume'),
            'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
            'info': ticker,
        }, market, False)

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = await self.publicGetV2Ticker24hr(self.extend(request, params))
        #
        #     {
        #         "symbol":"ETH/BTC",
        #         "priceChange":"0.00030",
        #         "priceChangePercent":"1.21",
        #         "weightedAvgPrice":"0.02481",
        #         "prevClosePrice":"0.02447",
        #         "lastPrice":"0.02477",
        #         "lastQty":"60.0",
        #         "bidPrice":"0.02477",
        #         "askPrice":"0.02484",
        #         "openPrice":"0.02447",
        #         "highPrice":"0.02524",
        #         "lowPrice":"0.02438",
        #         "volume":"11.97",
        #         "quoteVolume":"0.298053",
        #         "openTime":1590969600000,
        #         "closeTime":1591000072693
        #     }
        #
        return self.parse_ticker(response, market)

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        response = await self.publicGetV2Ticker24hr(params)
        #
        #     [
        #         {
        #              "symbol": "SHIB/USD_LEVERAGE",
        #              "weightedAvgPrice": "0.000027595",
        #              "lastPrice": "0.00002737",
        #              "lastQty": "1.11111111E8",
        #              "bidPrice": "0.00002737",
        #              "askPrice": "0.00002782",
        #              "highPrice": "0.00002896",
        #              "lowPrice": "0.00002738",
        #              "volume": "16472160000",
        #              "quoteVolume": "454796.3376",
        #              "openTime": "1645187472000",
        #              "closeTime": "1645273872000",
        #         }
        #     ]
        #
        return self.parse_tickers(response, symbols)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1590971040000,
        #         "0.02454",
        #         "0.02456",
        #         "0.02452",
        #         "0.02456",
        #         249
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'interval': self.timeframes[timeframe],
        }
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit  # default 500, max 1000
        response = await self.publicGetV2Klines(self.extend(request, params))
        #
        #     [
        #         [1590971040000,"0.02454","0.02456","0.02452","0.02456",249],
        #         [1590971100000,"0.02455","0.02457","0.02452","0.02456",300],
        #         [1590971160000,"0.02455","0.02456","0.02453","0.02454",286],
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public aggregate trades)
        #
        #     {
        #         "a":"1658318071",    # Aggregate tradeId
        #         "p":"0.02476",       # Price
        #         "q":"0.0",           # Official doc says: "Quantity(should be ignored)"
        #         "T":"1591001423382",  # Epoch timestamp in MS
        #         "m":false            # Was the buyer the maker
        #     }
        #
        # createOrder fills(private)
        #
        #     {
        #         "price": "9807.05",
        #         "qty": "0.01",
        #         "commission": "0",
        #         "commissionAsset": "dUSD"
        #     }
        #
        # fetchMyTrades
        #
        #     {
        #         "symbol": "DOGE/USD",
        #         "id": "116046000",
        #         "orderId": "00000000-0000-0000-0000-000006dbb8ad",
        #         "price": "0.14094",
        #         "qty": "40.0",
        #         "commission": "0.01",
        #         "commissionAsset": "USD",
        #         "time": "1645283022351",
        #         "buyer": False,
        #         "maker": False,
        #         "isBuyer": False,
        #         "isMaker": False
        #     }
        #
        timestamp = self.safe_integer_2(trade, 'T', 'time')
        priceString = self.safe_string_2(trade, 'p', 'price')
        amountString = self.safe_string_2(trade, 'q', 'qty')
        id = self.safe_string_2(trade, 'a', 'id')
        side = None
        orderId = self.safe_string(trade, 'orderId')
        takerOrMaker = None
        if 'm' in trade:
            side = 'sell' if trade['m'] else 'buy'  # self is reversed intentionally [TODO: needs reason to be mentioned]
            takerOrMaker = 'taker'  # in public trades, it's always taker
        elif 'isBuyer' in trade:
            side = 'buy' if (trade['isBuyer']) else 'sell'  # self is a True side
            takerOrMaker = 'maker' if trade['isMaker'] else 'taker'
        fee = None
        if 'commission' in trade:
            fee = {
                'cost': self.safe_string(trade, 'commission'),
                'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAsset')),
            }
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        return self.safe_trade({
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
            'info': trade,
        }, market)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            # 'limit': 500,  # default 500, max 1000
        }
        if limit is not None:
            request['limit'] = limit  # default 500, max 1000
        if since is not None:
            request['startTime'] = since
        response = await self.publicGetV2AggTrades(self.extend(request, params))
        #
        # [
        #     {
        #         "a":"1658318071",    # Aggregate tradeId
        #         "p":"0.02476",       # Price
        #         "q":"0.0",           # Official doc says: "Quantity(should be ignored)"
        #         "T":"1591001423382",  # Epoch timestamp in MS
        #         "m":false            # Was the buyer the maker
        #     },
        # ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        # limit
        #
        #     {
        #         "symbol": "BTC/USD",
        #         "orderId": "00000000-0000-0000-0000-000006eacaa0",
        #         "transactTime": "1645281669295",
        #         "price": "30000.00000000",
        #         "origQty": "0.0002",
        #         "executedQty": "0.0",  # positive for BUY, negative for SELL
        #         "status": "NEW",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #     }
        #
        # market
        #
        #     {
        #         "symbol": "DOGE/USD",
        #         "orderId": "00000000-0000-0000-0000-000006eab2ad",
        #         "transactTime": "1645283022252",
        #         "price": "0.14066000",
        #         "origQty": "40",
        #         "executedQty": "40.0",  # positive for BUY, negative for SELL
        #         "status": "FILLED",
        #         "timeInForce": "FOK",
        #         "type": "MARKET",
        #         "side": "SELL",
        #         "fills": [
        #             {
        #                 "price": "0.14094",
        #                 "qty": "40.0",
        #                 "commission": "0",
        #                 "commissionAsset": "dUSD",
        #             },
        #         ],
        #     }
        #
        # cancelOrder
        #
        #     {
        #         "symbol": "DOGE/USD",
        #         "orderId": "00000000-0000-0003-0000-000006db714c",
        #         "price": "0.13",
        #         "origQty": "30.0",
        #         "executedQty": "0.0",
        #         "status": "CANCELED",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #     }
        #
        # fetchOpenOrders
        #
        #   {
        #       "symbol": "DOGE/USD",
        #       "orderId": "00000000-0000-0003-0000-000004bcc27a",
        #       "price": "0.13",
        #       "origQty": "39.0",
        #       "executedQty": "0.0",
        #       "status": "NEW",
        #       "timeInForce": "GTC",
        #       "type": "LIMIT",
        #       "side": "BUY",
        #       "time": "1645284216240",
        #       "updateTime": "1645284216240",
        #       "leverage": False,  # whether it's swap or not
        #       "working": True,
        #   }
        #
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market, '/')
        id = self.safe_string(order, 'orderId')
        price = self.safe_string(order, 'price')
        amount = self.safe_string(order, 'origQty')
        filledRaw = self.safe_string(order, 'executedQty')
        filled = Precise.string_abs(filledRaw)
        status = self.parse_order_status(self.safe_string(order, 'status'))
        timeInForce = self.parse_order_time_in_force(self.safe_string(order, 'timeInForce'))
        type = self.parse_order_type(self.safe_string(order, 'type'))
        side = self.parse_order_side(self.safe_string(order, 'side'))
        timestamp = self.safe_integer_2(order, 'time', 'transactTime')
        fills = self.safe_value(order, 'fills')
        return self.safe_order({
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': None,
            'trades': fills,
        }, market)

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

    def parse_order_type(self, status):
        statuses = {
            'MARKET': 'market',
            'LIMIT': 'limit',
            'STOP': 'stop',
            # temporarily we remove custom mappings
            # 'LIMIT_MAKER': '',
            # 'STOP_LOSS': 'stop-loss',
            # 'STOP_LOSS_LIMIT': 'stop-limit',
            # 'TAKE_PROFIT': 'take-profit',
            # 'TAKE_PROFIT_LIMIT': 'take-profit',
        }
        return self.safe_string(statuses, status, status)

    def parse_order_time_in_force(self, status):
        statuses = {
            'GTC': 'GTC',
            'FOK': 'FOK',
            'IOC': 'IOC',
        }
        return self.safe_string(statuses, status, status)

    def parse_order_side(self, status):
        statuses = {
            'BUY': 'buy',
            'SELL': 'sell',
        }
        return self.safe_string(statuses, status, status)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        accountId = None
        if market['margin']:
            accountId = self.safe_integer(params, 'accountId')
            if accountId is None:
                raise ArgumentsRequired(self.id + ' createOrder() requires an accountId parameter for ' + market['type'] + ' market ' + symbol)
        newOrderRespType = self.safe_value(self.options['newOrderRespType'], type, 'RESULT')
        request = {
            'symbol': market['id'],
            'quantity': self.amount_to_precision(symbol, amount),
            'type': type.upper(),
            'side': side.upper(),
            'newOrderRespType': newOrderRespType,  # 'RESULT' for full order or 'FULL' for order with fills
            # 'leverage': 1,
            # 'accountId': 5470306579272968,  # required for leverage markets
            # 'takeProfit': '123.45',
            # 'stopLoss': '54.321',
            # 'guaranteedStopLoss': '54.321',
        }
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
            request['timeInForce'] = self.options['defaultTimeInForce']
        elif type == 'stop':
            request['price'] = self.price_to_precision(symbol, price)
        response = await self.privatePostV2Order(self.extend(request, params))
        #
        # limit
        #
        #     {
        #         "symbol": "BTC/USD",
        #         "orderId": "00000000-0000-0000-0000-000006eaaaa0",
        #         "transactTime": "1645281669295",
        #         "price": "30000.00000000",
        #         "origQty": "0.0002",
        #         "executedQty": "0.0",  # positive for BUY, negative for SELL
        #         "status": "NEW",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #     }
        #
        # market
        #
        #     {
        #         "symbol": "DOGE/USD",
        #         "orderId": "00000000-0000-0000-0000-000006eab8ad",
        #         "transactTime": "1645283022252",
        #         "price": "0.14066000",
        #         "origQty": "40",
        #         "executedQty": "40.0",  # positive for BUY, negative for SELL
        #         "status": "FILLED",
        #         "timeInForce": "FOK",
        #         "type": "MARKET",
        #         "side": "BUY",
        #         "fills": [
        #             {
        #                 "price": "0.14094",
        #                 "qty": "40.0",
        #                 "commission": "0",
        #                 "commissionAsset": "dUSD"
        #             }
        #         ]
        #     }
        #
        return self.parse_order(response, market)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        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.')
        response = await self.privateGetV2OpenOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "symbol": "DOGE/USD",
        #             "orderId": "00000000-0000-0003-0000-000004bac57a",
        #             "price": "0.13",
        #             "origQty": "39.0",
        #             "executedQty": "0.0",  # positive for BUY, negative for SELL
        #             "status": "NEW",
        #             "timeInForce": "GTC",
        #             "type": "LIMIT",
        #             "side": "BUY",
        #             "time": "1645284216240",
        #             "updateTime": "1645284216240",
        #             "leverage": False,
        #             "working": True
        #         },
        #     ]
        #
        return self.parse_orders(response, market, since, limit, params)

    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)
        origClientOrderId = self.safe_value(params, 'origClientOrderId')
        request = {
            'symbol': market['id'],
            # 'orderId': int(id),
            # 'origClientOrderId': id,
        }
        if origClientOrderId is None:
            request['orderId'] = id
        else:
            request['origClientOrderId'] = origClientOrderId
        response = await self.privateDeleteV2Order(self.extend(request, params))
        #
        #     {
        #         "symbol": "DOGE/USD",
        #         "orderId": "00000000-0000-0003-0000-000006db764c",
        #         "price": "0.13",
        #         "origQty": "30.0",
        #         "executedQty": "0.0",  # positive for BUY, negative for SELL
        #         "status": "CANCELED",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #     }
        #
        return self.parse_order(response, market)

    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)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetV2MyTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "symbol": "DOGE/USD",
        #             "id": "116046000",
        #             "orderId": "00000000-0000-0000-0000-000006dbb8ad",
        #             "price": "0.14094",
        #             "qty": "40.0",
        #             "commission": "0.01",
        #             "commissionAsset": "USD",
        #             "time": "1645283022351",
        #             "buyer": False,
        #             "maker": False,
        #             "isBuyer": False,
        #             "isMaker": False
        #         },
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        return await self.fetch_transactions_by_method('privateGetV2Deposits', code, since, limit, params)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        return await self.fetch_transactions_by_method('privateGetV2Withdrawals', code, since, limit, params)

    async def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        return await self.fetch_transactions_by_method('privateGetV2Transactions', code, since, limit, params)

    async def fetch_transactions_by_method(self, method, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     [
        #       {
        #         "id": "616769213",
        #         "balance": "2.088",
        #         "amount": "1.304",   # negative for 'withdrawal'
        #         "currency": "CAKE",
        #         "type": "deposit",
        #         "timestamp": "1645282121023",
        #         "paymentMethod": "BLOCKCHAIN",
        #         "blockchainTransactionHash": "0x57c68c1f2ae74d5eda5a2a00516361d241a5c9e1ee95bf32573523857c38c112",
        #         "status": "PROCESSED",
        #         "commission": "0.14",  # self property only exists in withdrawal
        #       },
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit, params)

    def parse_transaction(self, transaction, currency=None):
        id = self.safe_string(transaction, 'id')
        txHash = self.safe_string(transaction, 'blockchainTransactionHash')
        amount = self.safe_number(transaction, 'amount')
        timestamp = self.safe_integer(transaction, 'timestamp')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        state = self.parse_transaction_status(self.safe_string(transaction, 'state'))
        type = self.parse_transaction_type(self.safe_string(transaction, 'type'))
        feeCost = self.safe_string(transaction, 'commission')
        fee = None
        if feeCost is not None:
            fee = {'currency': code, 'cost': feeCost}
        result = {
            'id': id,
            'txid': txHash,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'addressFrom': None,
            'address': None,
            'addressTo': None,
            'tagFrom': None,
            'tag': None,
            'tagTo': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': state,
            'updated': None,
            'comment': None,
            'fee': fee,
            'info': transaction,
        }
        return result

    def parse_transaction_status(self, status):
        statuses = {
            'APPROVAL': 'pending',
            'PROCESSED': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction_type(self, type):
        types = {
            'deposit': 'deposit',
            'withdrawal': 'withdrawal',
        }
        return self.safe_string(types, type, type)

    async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetV2Ledger(self.extend(request, params))
        # in the below example, first item expresses withdrawal/deposit type, second example expresses trade
        #
        # [
        #     {
        #       "id": "619031398",
        #       "balance": "0.0",
        #       "amount": "-1.088",
        #       "currency": "CAKE",
        #       "type": "withdrawal",
        #       "timestamp": "1645460496425",
        #       "commission": "0.13",
        #       "paymentMethod": "BLOCKCHAIN",  # present in withdrawal/deposit
        #       "blockchainTransactionHash": "0x400ac905557c3d34638b1c60eba110b3ee0f97f4eb0f7318015ab76e7f16b7d6",  # present in withdrawal/deposit
        #       "status": "PROCESSED"
        #     },
        #     {
        #       "id": "619031034",
        #       "balance": "8.17223588",
        #       "amount": "-0.01326294",
        #       "currency": "USD",
        #       "type": "exchange_commission",
        #       "timestamp": "1645460461235",
        #       "commission": "0.01326294",
        #       "status": "PROCESSED"
        #     },
        # ]
        #
        return self.parse_ledger(response, currency, since, limit)

    def parse_ledger_entry(self, item, currency=None):
        id = self.safe_string(item, 'id')
        amountString = self.safe_string(item, 'amount')
        amount = Precise.string_abs(amountString)
        timestamp = self.safe_integer(item, 'timestamp')
        currencyId = self.safe_string(item, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        feeCost = self.safe_string(item, 'commission')
        fee = None
        if feeCost is not None:
            fee = {'currency': code, 'cost': feeCost}
        direction = 'out' if Precise.string_lt(amountString, '0') else 'in'
        result = {
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'direction': direction,
            'account': None,
            'referenceId': self.safe_string(item, 'blockchainTransactionHash'),
            'referenceAccount': None,
            'type': self.parse_ledger_entry_type(self.safe_string(item, 'type')),
            'currency': code,
            'amount': amount,
            'before': None,
            'after': self.safe_string(item, 'balance'),
            'status': self.parse_ledger_entry_status(self.safe_string(item, 'status')),
            'fee': fee,
            'info': item,
        }
        return result

    def parse_ledger_entry_status(self, status):
        statuses = {
            'APPROVAL': 'pending',
            'PROCESSED': 'ok',
            'CANCELLED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_ledger_entry_type(self, type):
        types = {
            'deposit': 'transaction',
            'withdrawal': 'transaction',
            'exchange_commission': 'fee',
        }
        return self.safe_string(types, type, type)

    async def fetch_leverage(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = await self.privateGetV2LeverageSettings(self.extend(request, params))
        #
        # {
        #     "values": [1, 2, 5, 10,],
        #     "value": "10",
        # }
        #
        return self.safe_number(response, 'value')

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
        }
        response = await self.privateGetV2DepositAddress(self.extend(request, params))
        #
        #     {"address":"0x97d64eb014ac779194991e7264f01c74c90327f0"}
        #
        return self.parse_deposit_address(response, currency)

    def parse_deposit_address(self, depositAddress, currency=None):
        address = self.safe_string(depositAddress, 'address')
        self.check_address(address)
        currency = self.safe_currency(None, currency)
        return {
            'currency': currency['code'],
            'address': address,
            'tag': None,
            'network': None,
            'info': depositAddress,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/' + path
        if path == 'historicalTrades':
            headers = {
                'X-MBX-APIKEY': self.apiKey,
            }
        if api == 'private':
            self.check_required_credentials()
            query = self.urlencode(self.extend({
                'timestamp': self.nonce(),
                'recvWindow': self.options['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'):
                url += '?' + query
            else:
                body = query
                headers['Content-Type'] = 'application/x-www-form-urlencoded'
        else:
            if params:
                url += '?' + self.urlencode(params)
        url = self.implode_hostname(url)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    async def fetch_positions(self, symbols=None, params={}):
        await self.load_markets()
        response = await self.privateGetV2TradingPositions(params)
        #
        # {
        #     "positions": [
        #       {
        #         "accountId": "109698017416453793",
        #         "id": "00a18490-0079-54c4-0000-0000803e73d3",
        #         "instrumentId": "45463225268524228",
        #         "orderId": "00a18490-0079-54c4-0000-0000803e73d2",
        #         "openQuantity": "13.6",
        #         "openPrice": "0.75724",
        #         "closeQuantity": "0.0",
        #         "closePrice": "0",
        #         "rpl": "-0.007723848",
        #         "rplConverted": "0",
        #         "upl": "-0.006664",
        #         "uplConverted": "-0.006664",
        #         "swap": "0",
        #         "swapConverted": "0",
        #         "fee": "-0.007723848",
        #         "dividend": "0",
        #         "margin": "0.2",
        #         "state": "ACTIVE",
        #         "currency": "USD",
        #         "createdTimestamp": "1645473877236",
        #         "openTimestamp": "1645473877193",
        #         "type": "NET",
        #         "cost": "2.0583600",
        #         "symbol": "XRP/USD_LEVERAGE"
        #       }
        #     ]
        # }
        #
        data = self.safe_value(response, 'positions', [])
        return self.parse_positions(data)

    def parse_positions(self, positions):
        result = []
        for i in range(0, len(positions)):
            result.append(self.parse_position(positions[i]))
        return result

    def parse_position(self, position, market=None):
        market = self.safe_market(self.safe_string(position, 'symbol'), market)
        symbol = market['symbol']
        timestamp = self.safe_number(position, 'createdTimestamp')
        quantityRaw = self.safe_string(position, 'openQuantity')
        side = 'long' if Precise.string_gt(quantityRaw, '0') else 'short'
        quantity = Precise.string_abs(quantityRaw)
        entryPrice = self.safe_number(position, 'openPrice')
        unrealizedProfit = self.safe_number(position, 'upl')
        marginCoeff = self.safe_string(position, 'margin')
        leverage = Precise.string_div('1', marginCoeff)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'contracts': self.parse_number(quantity),
            'contractSize': None,
            'entryPrice': entryPrice,
            'collateral': None,
            'side': side,
            # 'realizedProfit': self.safe_number(position, 'rpl'),
            'unrealizedProfit': unrealizedProfit,
            'leverage': leverage,
            'percentage': None,
            'marginType': None,
            'notional': None,
            'markPrice': None,
            'liquidationPrice': None,
            'initialMargin': None,
            'initialMarginPercentage': None,
            'maintenanceMargin': self.parse_number(marginCoeff),
            'maintenanceMarginPercentage': None,
            'marginRatio': None,
            'info': position,
        }

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if (httpCode == 418) or (httpCode == 429):
            raise DDoSProtection(self.id + ' ' + str(httpCode) + ' ' + 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 httpCode >= 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
        #
        #     {"code":-1128,"msg":"Combination of optional parameters invalid."}
        #
        errorCode = self.safe_string(response, 'code')
        if (errorCode is not None) and (errorCode != '0'):
            feedback = self.id + ' ' + self.json(response)
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            message = self.safe_string(response, 'msg')
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)
