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

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

from ccxt.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.decimal_to_precision import TICK_SIZE


class bkex(Exchange):

    def describe(self):
        return self.deep_extend(super(bkex, self).describe(), {
            'id': 'bkex',
            'name': 'BKEX',
            'countries': ['BVI'],  # British Virgin Islands
            'rateLimit': 100,
            'version': 'v2',
            'certified': False,
            'has': {
                'CORS': None,
                'spot': None,
                'margin': None,
                'swap': None,
                'future': None,
                'option': None,
                'addMargin': None,
                'cancelAllOrders': None,
                'cancelOrder': True,
                'cancelOrders': True,
                'createDepositAddress': None,
                'createLimitOrder': None,
                'createMarketOrder': None,
                'createOrder': True,
                'editOrder': None,
                'fetchAccounts': None,
                'fetchBalance': True,
                'fetchBidsAsks': None,
                'fetchBorrowRate': None,
                'fetchBorrowRateHistory': None,
                'fetchBorrowRates': None,
                'fetchBorrowRatesPerSymbol': None,
                'fetchCanceledOrders': None,
                'fetchClosedOrder': None,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDeposit': False,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': None,
                'fetchDepositAddressesByNetwork': None,
                'fetchDeposits': True,
                'fetchFundingHistory': None,
                'fetchFundingRate': None,
                'fetchFundingRateHistory': None,
                'fetchFundingRates': None,
                'fetchIndexOHLCV': None,
                'fetchL2OrderBook': None,
                'fetchLedger': None,
                'fetchLedgerEntry': None,
                'fetchLeverageTiers': None,
                'fetchMarginMode': False,
                'fetchMarketLeverageTiers': None,
                'fetchMarkets': True,
                'fetchMarkOHLCV': None,
                'fetchMyTrades': None,
                'fetchOHLCV': True,
                'fetchOpenOrder': True,
                'fetchOpenOrders': True,
                'fetchOrder': False,
                'fetchOrderBook': True,
                'fetchOrderBooks': None,
                'fetchOrders': None,
                'fetchOrderTrades': None,
                'fetchPosition': None,
                'fetchPositionMode': False,
                'fetchPositions': None,
                'fetchPositionsRisk': None,
                'fetchPremiumIndexOHLCV': None,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTradingLimits': None,
                'fetchTransactionFee': None,
                'fetchTransactionFees': None,
                'fetchTransactions': None,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': True,
                'privateAPI': True,
                'publicAPI': True,
                'reduceMargin': None,
                'setLeverage': None,
                'setMarginMode': None,
                'setPositionMode': None,
                'signIn': None,
                'transfer': False,
                'withdraw': False,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '6h': '6h',
                '12h': '12h',
                '1d': '1d',
                '1w': '1w',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/158043180-bb079a65-69e8-45a2-b393-f094d334e610.jpg',
                'api': {
                    'public': 'https://api.bkex.com',
                    'private': 'https://api.bkex.com',
                },
                'www': 'https://www.bkex.com/',
                'doc': [
                    'https://bkexapi.github.io/docs/api_en.htm',
                ],
                'fees': [
                    'https://www.bkex.com/help/instruction/33',
                ],
            },
            'api': {
                'public': {
                    'get': {
                        '/common/symbols': 1,
                        '/common/currencys': 1,
                        '/common/timestamp': 1,
                        '/q/kline': 1,
                        '/q/tickers': 1,
                        '/q/ticker/price': 1,
                        '/q/depth': 1,
                        '/q/deals': 1,
                        # contracts:
                        '/contract/common/brokerInfo': 1,
                        '/contract/q/index': 1,
                        '/contract/q/depth': 1,
                        '/contract/q/depthMerged': 1,
                        '/contract/q/trades': 1,
                        '/contract/q/kline': 1,
                        '/contract/q/ticker24hr': 1,
                    },
                },
                'private': {
                    'get': {
                        '/u/api/info': 1,
                        '/u/account/balance': 1,
                        '/u/wallet/address': 1,
                        '/u/wallet/depositRecord': 1,
                        '/u/wallet/withdrawRecord': 1,
                        '/u/order/openOrders': 1,
                        '/u/order/openOrder/detail': 1,
                        '/u/order/historyOrders': 1,
                        # contracts:
                        '/contract/trade/getOrder': 1,
                        '/contract/trade/openOrders': 1,
                        '/contract/trade/historyOrders': 1,
                        '/contract/trade/myTrades': 1,
                        '/contract/trade/positions': 1,
                        '/contract/u/account': 1,
                    },
                    'post': {
                        '/u/account/transfer': 1,
                        '/u/wallet/withdraw': 1,
                        '/u/order/create': 1,
                        '/u/order/cancel': 1,
                        '/u/order/batchCreate': 1,
                        '/u/order/batchCancel': 1,
                        # contracts:
                        '/contract/trade/order': 1,
                        '/contract/trade/orderCancel': 1,
                        '/contract/trade/modifyMargin': 1,
                        '/contract/ws/dataStream/create': 1,
                        '/contract/ws/dataStream/update': 1,
                        '/contract/ws/dataStream/delete': 1,
                    },
                    'delete': {
                    },
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': self.parse_number('0.0015'),
                    'taker': self.parse_number('0.002'),
                },
            },
            'options': {
                'timeframes': {
                    'spot': {
                    },
                    'contract': {
                    },
                },
                'defaultType': 'spot',  # spot, swap
                'networks': {
                    'TRX': 'TRC-20',
                    'TRC20': 'TRC-20',
                    'ETH': 'ERC-20',
                    'ERC20': 'ERC-20',
                    'BEP20': 'BEP-20(BSC)',
                },
            },
            'commonCurrencies': {
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    '1005': InsufficientFunds,
                },
                'broad': {
                    'Not Enough balance': InsufficientFunds,
                    'Order does not exist': InvalidOrder,
                    'System busy, please try again later': BadRequest,  # in my tests, self was thrown mostly when request was bad, not the problem of exchange. It is easily reproduced in 'cancelOrders'
                },
            },
        })

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for bkex
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        response = self.publicGetCommonSymbols(params)
        #
        # {
        #     "code": "0",
        #     "data": [
        #         {
        #             "minimumOrderSize": "0",
        #             "minimumTradeVolume": "0E-18",
        #             "pricePrecision": "11",
        #             "supportTrade": True,
        #             "symbol": "COMT_USDT",
        #             "volumePrecision": 0
        #         },
        #     ],
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data', [])
        result = []
        for i in range(0, len(data)):
            market = data[i]
            id = self.safe_string(market, 'symbol')
            baseId, quoteId = id.split('_')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            result.append({
                'id': id,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'future': False,
                'swap': False,
                'option': False,
                'active': self.safe_value(market, 'supportTrade'),
                'contract': False,
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'volumePrecision'))),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'pricePrecision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(market, 'minimumOrderSize'),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_number(market, 'minimumTradeVolume'),
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.publicGetCommonCurrencys(params)
        #
        # {
        #     "code": "0",
        #     "data": [
        #        {
        #           "currency": "ETH",
        #           "maxWithdrawOneDay": "100.000000000000000000",
        #           "maxWithdrawSingle": "50.000000000000000000",
        #           "minWithdrawSingle": "0.005000000000000000",
        #           "supportDeposit": True,
        #           "supportTrade": True,
        #           "supportWithdraw": True,
        #           "withdrawFee": 0.01
        #        },
        #     ],
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data', [])
        result = {}
        for i in range(0, len(data)):
            currency = data[i]
            id = self.safe_string(currency, 'currency')
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'name')
            withdrawEnabled = self.safe_value(currency, 'supportWithdraw')
            depositEnabled = self.safe_value(currency, 'supportDeposit')
            tradeEnabled = self.safe_value(currency, 'supportTrade')
            active = withdrawEnabled and depositEnabled and tradeEnabled
            result[code] = {
                'id': id,
                'code': code,
                'name': name,
                'deposit': depositEnabled,
                'withdraw': withdrawEnabled,
                'active': active,
                'fee': self.safe_number(currency, 'withdrawFee'),
                'precision': None,
                'limits': {
                    'amount': {'min': None, 'max': None},
                    'price': {'min': None, 'max': None},
                    'cost': {'min': None, 'max': None},
                    'withdraw': {'min': self.safe_number(currency, 'minWithdrawSingle'), 'max': self.safe_number(currency, 'maxWithdrawSingle')},
                },
                'info': currency,
            }
        return result

    def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = self.publicGetCommonTimestamp(params)
        #
        # {
        #     "code": '0',
        #     "data": 1573542445411,
        #     "msg": "success",
        #     "status": 0
        # }
        #
        return self.safe_integer(response, 'data')

    def fetch_status(self, params={}):
        """
        the latest known information on the availability of the exchange API
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: a `status structure <https://docs.ccxt.com/en/latest/manual.html#exchange-status-structure>`
        """
        response = self.publicGetCommonTimestamp(params)
        #
        #     {
        #         "code": '0',
        #         "data": 1573542445411,
        #         "msg": "success",
        #         "status": 0
        #     }
        #
        statusRaw = self.safe_integer(response, 'status')
        codeRaw = self.safe_integer(response, 'code')
        updated = self.safe_integer(response, 'data')
        return {
            'status': 'ok' if (statusRaw == 0 and codeRaw == 0) else statusRaw,
            'updated': updated,
            'eta': None,
            'url': None,
            'info': response,
        }

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'period': self.timeframes[timeframe],
        }
        if limit is not None:
            request['size'] = limit
        # their docs says that 'from/to' arguments are mandatory, however that's not True in reality
        if since is not None:
            request['from'] = since
            # when 'since' [from] argument is set, then exchange also requires 'to' value to be set. So we have to set 'to' argument depending 'limit' amount(if limit was not provided, then exchange-default 500).
            if limit is None:
                limit = 500
            duration = self.parse_timeframe(timeframe)
            timerange = limit * duration * 1000
            request['to'] = self.sum(request['from'], timerange)
        response = self.publicGetQKline(request)
        #
        # {
        #     "code": "0",
        #     "data": [
        #       {
        #          "close": "43414.68",
        #          "high": "43446.47",
        #          "low": "43403.05",
        #          "open": "43406.05",
        #          "quoteVolume": "61500.40099",
        #          "symbol": "BTC_USDT",
        #          "ts": "1646152440000",
        #          "volume": 1.41627
        #       },
        #     ],
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        return [
            self.safe_integer(ohlcv, 'ts'),
            self.safe_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'volume'),
        ]

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.publicGetQTickers(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": [
        #       {
        #         "change": "6.52",
        #         "close": "43573.470000",
        #         "high": "44940.540000",
        #         "low": "40799.840000",
        #         "open": "40905.780000",
        #         "quoteVolume": "225621691.5991",
        #         "symbol": "BTC_USDT",
        #         "ts": "1646156490781",
        #         "volume": 5210.349
        #       }
        #     ],
        #     "msg": "success",
        #     "status": 0
        # }
        #
        tickers = self.safe_value(response, 'data')
        ticker = self.safe_value(tickers, 0)
        return self.parse_ticker(ticker, market)

    def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        :param [str]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        request = {}
        if symbols is not None:
            if not isinstance(symbols, list):
                raise BadRequest(self.id + ' fetchTickers() symbols argument should be an array')
        if symbols is not None:
            marketIds = self.market_ids(symbols)
            request['symbol'] = ','.join(marketIds)
        response = self.publicGetQTickers(self.extend(request, params))
        tickers = self.safe_value(response, 'data')
        return self.parse_tickers(tickers, symbols, params)

    def parse_ticker(self, ticker, market=None):
        #
        #    {
        #          "change":-0.46,
        #          "close":29664.46,
        #          "high":30784.99,
        #          "low":29455.36,
        #          "open":29803.38,
        #          "quoteVolume":714653752.6991,
        #          "symbol":"BTC_USDT",
        #          "ts":1652812048118,
        #          "volume":23684.9416
        #    }
        #
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_integer(ticker, 'ts')
        last = self.safe_string(ticker, 'close')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': self.safe_string(ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': self.safe_string(ticker, 'change'),  # 24h percentage change(close - open) / open * 100
            'average': None,
            'baseVolume': self.safe_string(ticker, 'volume'),
            'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
            'info': ticker,
        }, market)

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['depth'] = min(limit, 50)
        response = self.publicGetQDepth(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": {
        #       "ask": [
        #         ["43820.07","0.86947"],
        #         ["43820.25","0.07503"],
        #       ],
        #       "bid": [
        #         ["43815.94","0.43743"],
        #         ["43815.72","0.08901"],
        #       ],
        #       "symbol": "BTC_USDT",
        #       "timestamp": 1646161595841
        #     },
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data')
        return self.parse_order_book(data, market['symbol'], None, 'bid', 'ask')

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['size'] = min(limit, 50)
        response = self.publicGetQDeals(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": [
        #       {
        #         "direction": "S",
        #         "price": "43930.63",
        #         "symbol": "BTC_USDT",
        #         "ts": "1646224171992",
        #         "volume": 0.030653
        #       },  # first item is most recent
        #     ],
        #     "msg": "success",
        #     "status": 0
        # }
        #
        trades = self.safe_value(response, 'data')
        return self.parse_trades(trades, market, since, limit)

    def parse_trade(self, trade, market=None):
        timestamp = self.safe_integer(trade, 'ts')
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market)
        side = self.parse_trade_side(self.safe_string(trade, 'direction'))
        amount = self.safe_number(trade, 'volume')
        price = self.safe_number(trade, 'price')
        type = None
        takerOrMaker = 'taker'
        id = self.safe_string(trade, 'tid')
        if id is None:
            id = self.synthetic_trade_id(market, timestamp, side, amount, price, type, takerOrMaker)
        return self.safe_trade({
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'order': None,
            'type': type,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': None,
            'info': trade,
        }, market)

    def parse_trade_side(self, side):
        sides = {
            'B': 'buy',
            'S': 'sell',
        }
        return self.safe_string(sides, side, side)

    def synthetic_trade_id(self, market=None, timestamp=None, side=None, amount=None, price=None, orderType=None, takerOrMaker=None):
        # TODO: can be unified method? self approach is being used by multiple exchanges(mexc, woo-coinsbit, dydx, ...)
        id = ''
        if timestamp is not None:
            id = self.number_to_string(timestamp) + '-' + self.safe_string(market, 'id', '_')
            if side is not None:
                id += '-' + side
            if orderType is not None:
                id += '-' + orderType
            if takerOrMaker is not None:
                id += '-' + takerOrMaker
            if amount is not None:
                id += '-' + self.number_to_string(amount)
            if price is not None:
                id += '-' + self.number_to_string(price)
        return id

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        self.load_markets()
        query = self.omit(params, 'type')
        response = self.privateGetUAccountBalance(query)
        #
        # {
        #     "code": "0",
        #     "data": {
        #       "WALLET": [
        #         {
        #           "available": "0.221212121000000000",
        #           "currency": "PHX",
        #           "frozen": "0E-18",
        #           "total": 0.221212121
        #         },
        #         {
        #           "available": "44.959577229600000000",
        #           "currency": "USDT",
        #           "frozen": "0E-18",
        #           "total": 44.9595772296
        #         }
        #       ]
        #     },
        #     "msg": "success",
        #     "status": 0
        # }
        #
        balances = self.safe_value(response, 'data')
        wallets = self.safe_value(balances, 'WALLET', [])
        result = {'info': wallets}
        for i in range(0, len(wallets)):
            wallet = wallets[i]
            currencyId = wallet['currency']
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_number(wallet, 'available')
            account['used'] = self.safe_number(wallet, 'frozen')
            account['total'] = self.safe_number(wallet, 'total')
            result[code] = account
        return self.safe_balance(result)

    def fetch_deposit_address(self, code, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.privateGetUWalletAddress(self.extend(request, params))
        # NOTE: You can only retrieve addresses of already generated wallets - so should already have generated that COIN deposit address in UI. Otherwise, it seems from API you can't create/obtain addresses for those coins.
        #
        # {
        #     "code": "0",
        #     "data": [
        #       {
        #         "currency": "BTC",
        #         "address": "1m4k2yUKTSrX6SM9FGgvwMyxQbYtRVi2N",
        #         "memo": ""
        #       }
        #     ],
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data', {})
        return self.parse_deposit_address(data, currency)

    def parse_deposit_address(self, data, currency=None):
        depositObject = self.safe_value(data, 0)
        address = self.safe_string(depositObject, 'address')
        tag = self.safe_string(depositObject, 'memo')
        currencyId = self.safe_string(depositObject, 'currency')
        currency = self.safe_currency(currencyId, currency)
        return {
            'currency': currency['code'],
            'address': address,
            'tag': tag,
            'network': None,
            'info': data,
        }

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        """
        fetch all deposits made to an account
        :param str code: unified currency code
        :param int|None since: the earliest time in ms to fetch deposits for
        :param int|None limit: the maximum number of deposits structures to retrieve
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchDeposits() requires code argument')
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        if since is not None:
            request['startTime'] = since
            endTime = self.milliseconds()
            request['endTime'] = endTime
        if limit is not None:
            request['Size'] = limit  # Todo: id api-docs, 'size' is incorrectly required to be in Uppercase
        response = self.privateGetUWalletDepositRecord(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": {
        #       "data": [
        #         {
        #           "createTime": "1622274255000",
        #           "currency": "BNB",
        #           "fromAddress": "bnb10af52w77pkehgxhnwgeca50q2t2354q4xexa5y",
        #           "hash": "97B982F497782C2777C0F6AD16CEAAC65A93A364B684A23A71CFBB8C010DEEA6",
        #           "id": "2021052923441510234383337",
        #           "status": "0",
        #           "toAddress": "bnb13w64gkc42c0l45m2p5me4qn35z0a3ej9ldks3j_82784659",
        #           "volume": 0.073
        #         }
        #       ],
        #       "total": 1
        #     },
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data', {})
        dataInner = self.safe_value(data, 'data', [])
        for i in range(0, len(dataInner)):
            dataInner[i]['transactType'] = 'deposit'
        return self.parse_transactions(dataInner, currency, since, limit, params)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        """
        fetch all withdrawals made from an account
        :param str code: unified currency code
        :param int|None since: the earliest time in ms to fetch withdrawals for
        :param int|None limit: the maximum number of withdrawals structures to retrieve
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires code argument')
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        if since is not None:
            request['startTime'] = since
            endTime = self.milliseconds()
            request['endTime'] = endTime
        if limit is not None:
            request['Size'] = limit  # Todo: id api-docs, 'size' is incorrectly required to be in Uppercase
        response = self.privateGetUWalletWithdrawRecord(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": {
        #       "data": [
        #         {
        #           ...
        #         }
        #       ],
        #       "total": 1
        #     },
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data', {})
        dataInner = self.safe_value(data, 'data', [])
        for i in range(0, len(dataInner)):
            dataInner[i]['transactType'] = 'withdrawal'
        return self.parse_transactions(dataInner, currency, since, limit, params)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        # {
        #   "createTime": "1622274255000",
        #   "currency": "BNB",
        #   "fromAddress": "bnb10af52w77pkehgxhnwgeca50q2t2354q4xexa5y",
        #   "hash": "97B982F497782C2777C0F6AD16CEAAC65A93A364B684A23A71CFBB8C010DEEA6",
        #   "id": "2021052923441510234383337",
        #   "status": "0",
        #   "toAddress": "bnb13w64gkc42c0l45m2p5me4qn35z0a3ej9ldks3j_82784659",
        #   "volume": 0.073
        # }
        #
        id = self.safe_string(transaction, 'id')
        amount = self.safe_number(transaction, 'volume')
        addressTo = self.safe_value(transaction, 'toAddress', {})
        addressFrom = self.safe_string(transaction, 'fromAddress')
        txid = self.safe_string(transaction, 'hash')
        type = self.safe_string(transaction, 'transactType')
        timestamp = self.safe_integer(transaction, 'createTime')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        return {
            'id': id,
            'currency': code,
            'amount': amount,
            'network': None,
            'address': addressTo,
            'addressTo': addressTo,
            'addressFrom': addressFrom,
            'tag': None,
            'tagTo': None,
            'tagFrom': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': {
                'currency': code,
                'cost': None,
            },
            'info': transaction,
        }

    def parse_transaction_status(self, status):
        statuses = {
            '-1': 'failed',
            '0': 'ok',
            '3': 'pending',
            '5': 'pending',
        }
        return self.safe_string(statuses, status, status)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float|None price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        direction = 'BID' if (side == 'buy') else 'ASK'
        request = {
            'symbol': market['id'],
            'type': type.upper(),
            'volume': self.amount_to_precision(symbol, amount),
            'direction': direction,
        }
        if (type != 'market') and (price is not None):
            request['price'] = self.price_to_precision(symbol, price)
        response = self.privatePostUOrderCreate(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": "2022030302410146630023187",
        #     "msg": "Create Order Successfully",
        #     "status": 0
        # }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str|None symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol) if (symbol is not None) else None
        request = {
            'orderId': id,
        }
        response = self.privatePostUOrderCancel(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": "2022030303032700030025325",
        #     "status": 0
        # }
        #
        return self.parse_order(response, market)

    def cancel_orders(self, ids, symbol=None, params={}):
        """
        cancel multiple orders
        :param [str] ids: order ids
        :param str|None symbol: unified market symbol, default is None
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: an list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if not isinstance(ids, list):
            raise ArgumentsRequired(self.id + ' cancelOrders() ids argument should be an array')
        self.load_markets()
        request = {
            'orders': self.json(ids),
        }
        response = self.privatePostUOrderBatchCancel(self.extend(request, params))
        # {
        #     "code": 0,
        #     "msg": "success",
        #     "data": {
        #        "success": 2,
        #        "fail": 0,
        #        "results": ["2019062312313131231"," 2019063123131312313"]
        #     }
        # }
        data = self.safe_value(response, 'data')
        results = self.safe_value(data, 'results')
        market = self.market(symbol) if (symbol is not None) else None
        return self.parse_orders(results, market, None, None, params)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        :param str symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch open orders for
        :param int|None limit: the maximum number of  open orders structures to retrieve
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['size'] = limit  # Todo: id api-docs, 'size' is incorrectly required to be in Uppercase
        response = self.privateGetUOrderOpenOrders(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": {
        #       "data": [
        #         {
        #           "createdTime": "1646248301418",
        #           "dealVolume": "0E-18",
        #           "direction": "BID",
        #           "frozenVolumeByOrder": "2.421300000000000000",
        #           "id": "2022030303114141830007699",
        #           "price": "0.150000000000000000",
        #           "source": "WALLET",
        #           "status": "0",
        #           "symbol": "BKK_USDT",
        #           "totalVolume": "16.142000000000000000",
        #           "type": "LIMIT"
        #         }
        #       ],
        #       "pageRequest": {
        #         "asc": False,
        #         "orderBy": "id",
        #         "page": "1",
        #         "size": 10
        #       },
        #       "total": 1
        #     },
        #     "msg": "success",
        #     "status": 0
        # }
        #
        result = self.safe_value(response, 'data')
        innerData = self.safe_value(result, 'data')
        return self.parse_orders(innerData, market, since, limit, params)

    def fetch_open_order(self, id, symbol=None, params={}):
        """
        fetch an open order by it's id
        :param str id: order id
        :param str|None symbol: unified market symbol, default is None
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        request = {
            'orderId': id,
        }
        response = self.privateGetUOrderOpenOrderDetail(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": {
        #       "createdTime": "1646248301418",
        #       "dealAvgPrice": "0",
        #       "dealVolume": "0E-18",
        #       "direction": "BID",
        #       "frozenVolumeByOrder": "2.421300000000000000",
        #       "id": "2022030303114141830002452",
        #       "price": "0.150000000000000000",
        #       "source": "WALLET",
        #       "status": "0",
        #       "symbol": "BKK_USDT",
        #       "totalVolume": "16.142000000000000000",
        #       "type": "LIMIT",
        #       "updateTime": 1646248301418
        #     },
        #     "msg": "success",
        #     "status": 0
        # }
        #
        data = self.safe_value(response, 'data')
        market = self.market(symbol) if (symbol is not None) else None
        return self.parse_order(data, market)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple closed orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the bkex api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['size'] = limit  # Todo: id api-docs, 'size' is incorrectly required to be in Uppercase
        if since is not None:
            request['startTime'] = since
        response = self.privateGetUOrderHistoryOrders(self.extend(request, params))
        #
        # {
        #     "code": "0",
        #     "data": {
        #       "data": [
        #         {
        #           "createdTime": "1646247807000",
        #           "dealAvgPrice": "0",
        #           "dealVolume": "0",
        #           "direction": "BID",
        #           "frozenVolumeByOrder": "1.65",
        #           "id": "2022030303032700030025943",
        #           "price": "0.15",
        #           "source": "WALLET",
        #           "status": "2",
        #           "symbol": "BKK_USDT",
        #           "totalVolume": "11",
        #           "type": "LIMIT",
        #           "updateTime": 1646247852558
        #         },
        #       ],
        #       "pageRequest": {
        #         "asc": False,
        #         "orderBy": "id",
        #         "page": "1",
        #         "size": 10
        #       },
        #       "total": 6
        #     },
        #     "msg": "success",
        #     "status": 0
        # }
        #
        result = self.safe_value(response, 'data')
        innerData = self.safe_value(result, 'data')
        return self.parse_orders(innerData, market, since, limit, params)

    def parse_order(self, order, market=None):
        #
        # fetchOpenOrders
        #
        #  {
        #       "createdTime": "1646248301418",
        #       "dealVolume": "0E-18",
        #       "direction": "BID",
        #       "frozenVolumeByOrder": "2.421300000000000000",
        #       "id": "2022030303114141830007699",
        #       "price": "0.150000000000000000",
        #       "source": "WALLET",
        #       "status": "0",
        #       "symbol": "BKK_USDT",
        #       "totalVolume": "16.142000000000000000",
        #       "type": "LIMIT"
        #       "stopPrice":  "0.14",            # present only for 'stop' order types
        #       "operator":  ">="                # present only for 'stop' order types
        #       "dealAvgPrice": "0",             # only present in 'fetchOrder' & 'fetchClosedOrders'
        #       "updateTime": 1646248301418      # only present in 'fetchOrder' & 'fetchClosedOrders'
        #  }
        #
        timestamp = self.safe_integer(order, 'createdTime')
        updateTime = self.safe_integer(order, 'updateTime')
        filled = self.safe_string(order, 'dealVolume')
        side = self.parse_order_side(self.safe_string(order, 'direction'))
        id = self.safe_string_2(order, 'id', 'data')
        price = self.safe_string(order, 'price')
        rawStatus = self.safe_string(order, 'status')
        rawType = self.safe_string(order, 'type')
        type = self.parse_order_type(rawType)
        postOnly = False
        if rawType == 'LIMIT_MAKER':
            postOnly = True
        status = None
        if timestamp is not None:
            # cancelOrder handling
            status = self.parse_order_status(rawStatus)
        marketId = self.safe_string(order, 'symbol')
        market = self.safe_market(marketId, market)
        amount = self.safe_string(order, 'totalVolume')
        stopPrice = self.safe_number(order, 'stopPrice')
        average = self.safe_string(order, 'dealAvgPrice')
        return self.safe_order({
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': updateTime,
            'status': status,
            'symbol': market['symbol'],
            'type': type,
            'timeInForce': None,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'average': average,
            'amount': amount,
            'filled': filled,
            'remaining': None,
            'cost': None,
            'trades': None,
            'fee': None,
            'info': order,
        }, market)

    def parse_order_side(self, side):
        sides = {
            'BID': 'buy',
            'ASK': 'sell',
        }
        return self.safe_string(sides, side, side)

    def parse_order_status(self, status):
        statuses = {
            '0': 'open',
            '1': 'closed',
            '2': 'canceled',
            '3': 'open',
        }
        return self.safe_string(statuses, status, status)

    def parse_order_type(self, status):
        statuses = {
            'MARKET': 'market',
            'LIMIT': 'limit',
            'LIMIT_MAKER': 'limit',
            'STOP_LIMIT': 'limit',
        }
        return self.safe_string(statuses, status, status)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/' + self.version + self.implode_params(path, params)
        params = self.omit(params, self.extract_params(path))
        paramsSortedEncoded = ''
        if params:
            paramsSortedEncoded = self.rawencode(self.keysort(params))
            if method == 'GET':
                url += '?' + paramsSortedEncoded
        if api == 'private':
            self.check_required_credentials()
            signature = self.hmac(self.encode(paramsSortedEncoded), self.encode(self.secret), hashlib.sha256)
            headers = {
                'Cache-Control': 'no-cache',
                'Content-type': 'application/x-www-form-urlencoded',
                'X_ACCESS_KEY': self.apiKey,
                'X_SIGNATURE': signature,
            }
            if method != 'GET':
                body = paramsSortedEncoded
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        #
        # success
        #
        #   {
        #      "code": "0",
        #      "msg": "success",
        #      "status": 0,
        #      "data": [...],
        #   }
        #
        #
        # action error
        #
        #   {
        #     "code":1005,
        #     "msg":"BKK:Not Enough balance",
        #     "status":0
        #   }
        #
        #
        # HTTP error
        #
        #   {
        #      "timestamp": "1646041085490",
        #      "status": "403",
        #      "error": "Forbidden",
        #      "message": "签名错误",
        #      "path": "/whatever/incorrect/path"
        #   }
        #
        message = self.safe_value(response, 'msg')
        if message == 'success':
            return
        responseCode = self.safe_string(response, 'code')
        if responseCode != '0':
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], responseCode, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
            raise ExchangeError(feedback)
