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

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

from ccxt.async_support.base.exchange import Exchange
import json
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import OrderImmediatelyFillable
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class bitrue(Exchange):

    def describe(self):
        return self.deep_extend(super(bitrue, self).describe(), {
            'id': 'bitrue',
            'name': 'Bitrue',
            'countries': ['SG'],  # Singapore, Malta
            'rateLimit': 1000,
            'certified': False,
            'version': 'v1',
            # new metainfo interface
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': None,  # has but unimplemented
                'future': None,
                'option': False,
                'cancelAllOrders': False,
                'cancelOrder': True,
                'createOrder': True,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'fetchBalance': True,
                'fetchBidsAsks': True,
                'fetchBorrowRate': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': False,
                'fetchBorrowRatesPerSymbol': False,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': False,
                'fetchDeposits': True,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchPositionMode': False,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTransactionFees': False,
                'fetchTransactions': False,
                'fetchTransfers': False,
                'fetchWithdrawals': True,
                'transfer': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1H',
                '2h': '2H',
                '4h': '4H',
                '1d': '1D',
                '1w': '1W',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/139516488-243a830d-05dd-446b-91c6-c1f18fe30c63.jpg',
                'api': {
                    'v1': 'https://www.bitrue.com/api/v1',
                    'v2': 'https://www.bitrue.com/api/v2',
                    'kline': 'https://www.bitrue.com/kline-api',
                },
                'www': 'https://www.bitrue.com',
                'referral': 'https://www.bitrue.com/activity/task/task-landing?inviteCode=EZWETQE&cn=900000',
                'doc': [
                    'https://github.com/Bitrue-exchange/bitrue-official-api-docs',
                ],
                'fees': 'https://bitrue.zendesk.com/hc/en-001/articles/4405479952537',
            },
            'api': {
                'kline': {
                    'public': {
                        'get': {
                            'public.json': 1,
                            'public{currency}.json': 1,
                        },
                    },
                },
                'v1': {
                    'public': {
                        'get': {
                            'ping': 1,
                            'time': 1,
                            'exchangeInfo': 1,
                            'depth': {'cost': 1, 'byLimit': [[100, 1], [500, 5], [1000, 10]]},
                            'trades': 1,
                            'historicalTrades': 5,
                            'aggTrades': 1,
                            'ticker/24hr': {'cost': 1, 'noSymbol': 40},
                            'ticker/price': {'cost': 1, 'noSymbol': 2},
                            'ticker/bookTicker': {'cost': 1, 'noSymbol': 2},
                            'market/kline': 1,
                        },
                    },
                    'private': {
                        'get': {
                            'order': 1,
                            'openOrders': 1,
                            'allOrders': 5,
                            'account': 5,
                            'myTrades': {'cost': 5, 'noSymbol': 40},
                            'etf/net-value/{symbol}': 1,
                            'withdraw/history': 1,
                            'deposit/history': 1,
                        },
                        'post': {
                            'order': 4,
                            'withdraw/commit': 1,
                        },
                        'delete': {
                            'order': 1,
                        },
                    },
                },
                'v2': {
                    'private': {
                        'get': {
                            'myTrades': 5,
                        },
                    },
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': False,
                    'percentage': True,
                    'taker': self.parse_number('0.00098'),
                    'maker': self.parse_number('0.00098'),
                },
                'future': {
                    'trading': {
                        'feeSide': 'quote',
                        'tierBased': True,
                        'percentage': True,
                        'taker': self.parse_number('0.000400'),
                        'maker': self.parse_number('0.000200'),
                        'tiers': {
                            'taker': [
                                [self.parse_number('0'), self.parse_number('0.000400')],
                                [self.parse_number('250'), self.parse_number('0.000400')],
                                [self.parse_number('2500'), self.parse_number('0.000350')],
                                [self.parse_number('7500'), self.parse_number('0.000320')],
                                [self.parse_number('22500'), self.parse_number('0.000300')],
                                [self.parse_number('50000'), self.parse_number('0.000270')],
                                [self.parse_number('100000'), self.parse_number('0.000250')],
                                [self.parse_number('200000'), self.parse_number('0.000220')],
                                [self.parse_number('400000'), self.parse_number('0.000200')],
                                [self.parse_number('750000'), self.parse_number('0.000170')],
                            ],
                            'maker': [
                                [self.parse_number('0'), self.parse_number('0.000200')],
                                [self.parse_number('250'), self.parse_number('0.000160')],
                                [self.parse_number('2500'), self.parse_number('0.000140')],
                                [self.parse_number('7500'), self.parse_number('0.000120')],
                                [self.parse_number('22500'), self.parse_number('0.000100')],
                                [self.parse_number('50000'), self.parse_number('0.000080')],
                                [self.parse_number('100000'), self.parse_number('0.000060')],
                                [self.parse_number('200000'), self.parse_number('0.000040')],
                                [self.parse_number('400000'), self.parse_number('0.000020')],
                                [self.parse_number('750000'), self.parse_number('0')],
                            ],
                        },
                    },
                },
                'delivery': {
                    'trading': {
                        'feeSide': 'base',
                        'tierBased': True,
                        'percentage': True,
                        'taker': self.parse_number('0.000500'),
                        'maker': self.parse_number('0.000100'),
                        'tiers': {
                            'taker': [
                                [self.parse_number('0'), self.parse_number('0.000500')],
                                [self.parse_number('250'), self.parse_number('0.000450')],
                                [self.parse_number('2500'), self.parse_number('0.000400')],
                                [self.parse_number('7500'), self.parse_number('0.000300')],
                                [self.parse_number('22500'), self.parse_number('0.000250')],
                                [self.parse_number('50000'), self.parse_number('0.000240')],
                                [self.parse_number('100000'), self.parse_number('0.000240')],
                                [self.parse_number('200000'), self.parse_number('0.000240')],
                                [self.parse_number('400000'), self.parse_number('0.000240')],
                                [self.parse_number('750000'), self.parse_number('0.000240')],
                            ],
                            'maker': [
                                [self.parse_number('0'), self.parse_number('0.000100')],
                                [self.parse_number('250'), self.parse_number('0.000080')],
                                [self.parse_number('2500'), self.parse_number('0.000050')],
                                [self.parse_number('7500'), self.parse_number('0.0000030')],
                                [self.parse_number('22500'), self.parse_number('0')],
                                [self.parse_number('50000'), self.parse_number('-0.000050')],
                                [self.parse_number('100000'), self.parse_number('-0.000060')],
                                [self.parse_number('200000'), self.parse_number('-0.000070')],
                                [self.parse_number('400000'), self.parse_number('-0.000080')],
                                [self.parse_number('750000'), self.parse_number('-0.000090')],
                            ],
                        },
                    },
                },
            },
            # exchange-specific options
            'options': {
                # 'fetchTradesMethod': 'publicGetAggTrades',  # publicGetTrades, publicGetHistoricalTrades
                'fetchMyTradesMethod': 'v2PrivateGetMyTrades',  # v1PrivateGetMyTrades
                'hasAlreadyAuthenticatedSuccessfully': False,
                'recvWindow': 5 * 1000,  # 5 sec, binance default
                'timeDifference': 0,  # the difference between system clock and Binance clock
                'adjustForTimeDifference': False,  # controls the adjustment logic upon instantiation
                'parseOrderToPrecision': False,  # force amounts and costs in parseOrder to precision
                'newOrderRespType': {
                    'market': 'FULL',  # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
                    'limit': 'FULL',  # we change it from 'ACK' by default to 'FULL'(returns immediately if limit is not hit)
                },
                'networks': {
                    'SPL': 'SOLANA',
                    'SOL': 'SOLANA',
                    'DOGE': 'dogecoin',
                    'ADA': 'Cardano',
                },
            },
            'commonCurrencies': {
                'MIM': 'MIM Swarm',
            },
            'precisionMode': TICK_SIZE,
            # https://binance-docs.github.io/apidocs/spot/en/#error-codes-2
            'exceptions': {
                'exact': {
                    'System is under maintenance.': OnMaintenance,  # {"code":1,"msg":"System is under maintenance."}
                    'System abnormality': ExchangeError,  # {"code":-1000,"msg":"System abnormality"}
                    'You are not authorized to execute self request.': PermissionDenied,  # {"msg":"You are not authorized to execute self request."}
                    'API key does not exist': AuthenticationError,
                    'Order would trigger immediately.': OrderImmediatelyFillable,
                    'Stop price would trigger immediately.': OrderImmediatelyFillable,  # {"code":-2010,"msg":"Stop price would trigger immediately."}
                    'Order would immediately match and take.': OrderImmediatelyFillable,  # {"code":-2010,"msg":"Order would immediately match and take."}
                    'Account has insufficient balance for requested action.': InsufficientFunds,
                    'Rest API trading is not enabled.': ExchangeNotAvailable,
                    "You don't have permission.": PermissionDenied,  # {"msg":"You don't have permission.","success":false}
                    'Market is closed.': ExchangeNotAvailable,  # {"code":-1013,"msg":"Market is closed."}
                    'Too many requests. Please try again later.': DDoSProtection,  # {"msg":"Too many requests. Please try again later.","success":false}
                    '-1000': ExchangeNotAvailable,  # {"code":-1000,"msg":"An unknown error occured while processing the request."}
                    '-1001': ExchangeNotAvailable,  # 'Internal error; unable to process your request. Please try again.'
                    '-1002': AuthenticationError,  # 'You are not authorized to execute self request.'
                    '-1003': RateLimitExceeded,  # {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."}
                    '-1013': InvalidOrder,  # createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL
                    '-1015': RateLimitExceeded,  # 'Too many new orders; current limit is %s orders per %s.'
                    '-1016': ExchangeNotAvailable,  # 'This service is no longer available.',
                    '-1020': BadRequest,  # 'This operation is not supported.'
                    '-1021': InvalidNonce,  # 'your time is ahead of server'
                    '-1022': AuthenticationError,  # {"code":-1022,"msg":"Signature for self request is not valid."}
                    '-1100': BadRequest,  # createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'
                    '-1101': BadRequest,  # Too many parameters; expected %s and received %s.
                    '-1102': BadRequest,  # Param %s or %s must be sent, but both were empty
                    '-1103': BadRequest,  # An unknown parameter was sent.
                    '-1104': BadRequest,  # Not all sent parameters were read, read 8 parameters but was sent 9
                    '-1105': BadRequest,  # Parameter %s was empty.
                    '-1106': BadRequest,  # Parameter %s sent when not required.
                    '-1111': BadRequest,  # Precision is over the maximum defined for self asset.
                    '-1112': InvalidOrder,  # No orders on book for symbol.
                    '-1114': BadRequest,  # TimeInForce parameter sent when not required.
                    '-1115': BadRequest,  # Invalid timeInForce.
                    '-1116': BadRequest,  # Invalid orderType.
                    '-1117': BadRequest,  # Invalid side.
                    '-1118': BadRequest,  # New client order ID was empty.
                    '-1119': BadRequest,  # Original client order ID was empty.
                    '-1120': BadRequest,  # Invalid interval.
                    '-1121': BadSymbol,  # Invalid symbol.
                    '-1125': AuthenticationError,  # This listenKey does not exist.
                    '-1127': BadRequest,  # More than %s hours between startTime and endTime.
                    '-1128': BadRequest,  # {"code":-1128,"msg":"Combination of optional parameters invalid."}
                    '-1130': BadRequest,  # Data sent for paramter %s is not valid.
                    '-1131': BadRequest,  # recvWindow must be less than 60000
                    '-2008': AuthenticationError,  # {"code":-2008,"msg":"Invalid Api-Key ID."}
                    '-2010': ExchangeError,  # generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc...
                    '-2011': OrderNotFound,  # cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'
                    '-2013': OrderNotFound,  # fetchOrder(1, 'BTC/USDT') -> 'Order does not exist'
                    '-2014': AuthenticationError,  # {"code":-2014, "msg": "API-key format invalid."}
                    '-2015': AuthenticationError,  # "Invalid API-key, IP, or permissions for action."
                    '-2019': InsufficientFunds,  # {"code":-2019,"msg":"Margin is insufficient."}
                    '-3005': InsufficientFunds,  # {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."}
                    '-3006': InsufficientFunds,  # {"code":-3006,"msg":"Your borrow amount has exceed maximum borrow amount."}
                    '-3008': InsufficientFunds,  # {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."}
                    '-3010': ExchangeError,  # {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."}
                    '-3015': ExchangeError,  # {"code":-3015,"msg":"Repay amount exceeds borrow amount."}
                    '-3022': AccountSuspended,  # You account's trading is banned.
                    '-4028': BadRequest,  # {"code":-4028,"msg":"Leverage 100 is not valid"}
                    '-3020': InsufficientFunds,  # {"code":-3020,"msg":"Transfer out amount exceeds max amount."}
                    '-3041': InsufficientFunds,  # {"code":-3041,"msg":"Balance is not enough"}
                    '-5013': InsufficientFunds,  # Asset transfer failed: insufficient balance"
                    '-11008': InsufficientFunds,  # {"code":-11008,"msg":"Exceeding the account's maximum borrowable limit."}
                    '-4051': InsufficientFunds,  # {"code":-4051,"msg":"Isolated balance insufficient."}
                },
                'broad': {
                    'has no operation privilege': PermissionDenied,
                    'MAX_POSITION': InvalidOrder,  # {"code":-2010,"msg":"Filter failure: MAX_POSITION"}
                },
            },
        })

    def cost_to_precision(self, symbol, cost):
        return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['quote'], self.precisionMode, self.paddingMode)

    def currency_to_precision(self, code, fee, networkCode=None):
        # info is available in currencies only if the user has configured his api keys
        if self.safe_value(self.currencies[code], 'precision') is not None:
            return self.decimal_to_precision(fee, TRUNCATE, self.currencies[code]['precision'], self.precisionMode, self.paddingMode)
        else:
            return self.number_to_string(fee)

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

    async def fetch_status(self, params={}):
        """
        the latest known information on the availability of the exchange API
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns dict: a `status structure <https://docs.ccxt.com/en/latest/manual.html#exchange-status-structure>`
        """
        response = await self.v1PublicGetPing(params)
        #
        # empty means working status.
        #
        #     {}
        #
        keys = list(response.keys())
        keysLength = len(keys)
        formattedStatus = 'maintenance' if keysLength else 'ok'
        return {
            'status': formattedStatus,
            'updated': None,
            'eta': None,
            'url': None,
            'info': response,
        }

    async def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = await self.v1PublicGetTime(params)
        #
        #     {
        #         "serverTime":1635467280514
        #     }
        #
        return self.safe_integer(response, 'serverTime')

    def safe_network(self, networkId):
        uppercaseNetworkId = networkId.upper()
        networksById = {
            'Aeternity': 'Aeternity',
            'AION': 'AION',
            'Algorand': 'Algorand',
            'ASK': 'ASK',
            'ATOM': 'ATOM',
            'AVAX C-Chain': 'AVAX C-Chain',
            'bch': 'bch',
            'BCH': 'BCH',
            'BEP2': 'BEP2',
            'BEP20': 'BEP20',
            'Bitcoin': 'Bitcoin',
            'BRP20': 'BRP20',
            'Cardano': 'ADA',
            'CasinoCoin': 'CasinoCoin',
            'CasinoCoin XRPL': 'CasinoCoin XRPL',
            'Contentos': 'Contentos',
            'Dash': 'Dash',
            'Decoin': 'Decoin',
            'DeFiChain': 'DeFiChain',
            'DGB': 'DGB',
            'Divi': 'Divi',
            'dogecoin': 'DOGE',
            'EOS': 'EOS',
            'ERC20': 'ERC20',
            'ETC': 'ETC',
            'Filecoin': 'Filecoin',
            'FREETON': 'FREETON',
            'HBAR': 'HBAR',
            'Hedera Hashgraph': 'Hedera Hashgraph',
            'HRC20': 'HRC20',
            'ICON': 'ICON',
            'ICP': 'ICP',
            'Ignis': 'Ignis',
            'Internet Computer': 'Internet Computer',
            'IOTA': 'IOTA',
            'KAVA': 'KAVA',
            'KSM': 'KSM',
            'LiteCoin': 'LiteCoin',
            'Luna': 'Luna',
            'MATIC': 'MATIC',
            'Mobile Coin': 'Mobile Coin',
            'MonaCoin': 'MonaCoin',
            'Monero': 'Monero',
            'NEM': 'NEM',
            'NEP5': 'NEP5',
            'OMNI': 'OMNI',
            'PAC': 'PAC',
            'Polkadot': 'Polkadot',
            'Ravencoin': 'Ravencoin',
            'Safex': 'Safex',
            'SOLANA': 'SOL',
            'Songbird': 'Songbird',
            'Stellar Lumens': 'Stellar Lumens',
            'Symbol': 'Symbol',
            'Tezos': 'XTZ',
            'theta': 'theta',
            'THETA': 'THETA',
            'TRC20': 'TRC20',
            'VeChain': 'VeChain',
            'VECHAIN': 'VECHAIN',
            'Wanchain': 'Wanchain',
            'XinFin Network': 'XinFin Network',
            'XRP': 'XRP',
            'XRPL': 'XRPL',
            'ZIL': 'ZIL',
        }
        return self.safe_string_2(networksById, networkId, uppercaseNetworkId, networkId)

    async def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = await self.v1PublicGetExchangeInfo(params)
        #
        #     {
        #         "timezone":"CTT",
        #         "serverTime":1635464889117,
        #         "rateLimits":[
        #             {"rateLimitType":"REQUESTS_WEIGHT","interval":"MINUTES","limit":6000},
        #             {"rateLimitType":"ORDERS","interval":"SECONDS","limit":150},
        #             {"rateLimitType":"ORDERS","interval":"DAYS","limit":288000},
        #         ],
        #         "exchangeFilters":[],
        #         "symbols":[
        #             {
        #                 "symbol":"SHABTC",
        #                 "status":"TRADING",
        #                 "baseAsset":"sha",
        #                 "baseAssetPrecision":0,
        #                 "quoteAsset":"btc",
        #                 "quotePrecision":10,
        #                 "orderTypes":["MARKET","LIMIT"],
        #                 "icebergAllowed":false,
        #                 "filters":[
        #                     {"filterType":"PRICE_FILTER","minPrice":"0.00000001349","maxPrice":"0.00000017537","priceScale":10},
        #                     {"filterType":"LOT_SIZE","minQty":"1.0","minVal":"0.00020","maxQty":"1000000000","volumeScale":0},
        #                 ],
        #                 "defaultPrice":"0.0000006100",
        #             },
        #         ],
        #         "coins":[
        #           {
        #               coin: "near",
        #               coinFulName: "NEAR Protocol",
        #               chains: ["BEP20",],
        #               chainDetail: [
        #                 {
        #                     chain: "BEP20",
        #                     enableWithdraw: True,
        #                     enableDeposit: True,
        #                     withdrawFee: "0.2000",
        #                     minWithdraw: "5.0000",
        #                     maxWithdraw: "1000000000000000.0000",
        #                 },
        #               ],
        #           },
        #         ],
        #     }
        #
        result = {}
        coins = self.safe_value(response, 'coins', [])
        for i in range(0, len(coins)):
            currency = coins[i]
            id = self.safe_string(currency, 'coin')
            name = self.safe_string(currency, 'coinFulName')
            code = self.safe_currency_code(id)
            enableDeposit = self.safe_value(currency, 'enableDeposit')
            enableWithdraw = self.safe_value(currency, 'enableWithdraw')
            networkIds = self.safe_value(currency, 'chains', [])
            networks = {}
            for j in range(0, len(networkIds)):
                networkId = networkIds[j]
                network = self.safe_network(networkId)
                networks[network] = {
                    'info': networkId,
                    'id': networkId,
                    'network': network,
                    'active': None,
                    'fee': None,
                    'precision': None,
                    'limits': {
                        'withdraw': {
                            'min': None,
                            'max': None,
                        },
                    },
                }
            active = (enableWithdraw and enableDeposit)
            result[code] = {
                'id': id,
                'name': name,
                'code': code,
                'precision': None,
                'info': currency,
                'active': active,
                'deposit': enableDeposit,
                'withdraw': enableWithdraw,
                'networks': networks,
                'fee': self.safe_number(currency, 'withdrawFee'),
                # 'fees': fees,
                'limits': {
                    'withdraw': {
                        'min': self.safe_number(currency, 'minWithdraw'),
                        'max': self.safe_number(currency, 'maxWithdraw'),
                    },
                },
            }
        return result

    async def fetch_markets(self, params={}):
        """
        retrieves data on all markets for bitrue
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        response = await self.v1PublicGetExchangeInfo(params)
        #
        #     {
        #         "timezone":"CTT",
        #         "serverTime":1635464889117,
        #         "rateLimits":[
        #             {"rateLimitType":"REQUESTS_WEIGHT","interval":"MINUTES","limit":6000},
        #             {"rateLimitType":"ORDERS","interval":"SECONDS","limit":150},
        #             {"rateLimitType":"ORDERS","interval":"DAYS","limit":288000},
        #         ],
        #         "exchangeFilters":[],
        #         "symbols":[
        #             {
        #                 "symbol":"SHABTC",
        #                 "status":"TRADING",
        #                 "baseAsset":"sha",
        #                 "baseAssetPrecision":0,
        #                 "quoteAsset":"btc",
        #                 "quotePrecision":10,
        #                 "orderTypes":["MARKET","LIMIT"],
        #                 "icebergAllowed":false,
        #                 "filters":[
        #                     {"filterType":"PRICE_FILTER","minPrice":"0.00000001349","maxPrice":"0.00000017537","priceScale":10},
        #                     {"filterType":"LOT_SIZE","minQty":"1.0","minVal":"0.00020","maxQty":"1000000000","volumeScale":0},
        #                 ],
        #                 "defaultPrice":"0.0000006100",
        #             },
        #         ],
        #         "coins":[
        #             {
        #                 "coin":"sbr",
        #                 "coinFulName":"Saber",
        #                 "enableWithdraw":true,
        #                 "enableDeposit":true,
        #                 "chains":["SOLANA"],
        #                 "withdrawFee":"2.0",
        #                 "minWithdraw":"5.0",
        #                 "maxWithdraw":"1000000000000000",
        #             },
        #         ],
        #     }
        #
        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')
            lowercaseId = self.safe_string_lower(market, 'symbol')
            baseId = self.safe_string(market, 'baseAsset')
            quoteId = self.safe_string(market, 'quoteAsset')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            filters = self.safe_value(market, 'filters', [])
            filtersByType = self.index_by(filters, 'filterType')
            status = self.safe_string(market, 'status')
            priceFilter = self.safe_value(filtersByType, 'PRICE_FILTER', {})
            amountFilter = self.safe_value(filtersByType, 'LOT_SIZE', {})
            defaultPricePrecision = self.safe_string(market, 'pricePrecision')
            defaultAmountPrecision = self.safe_string(market, 'quantityPrecision')
            pricePrecision = self.safe_string(priceFilter, 'priceScale', defaultPricePrecision)
            amountPrecision = self.safe_string(amountFilter, 'volumeScale', defaultAmountPrecision)
            entry = {
                'id': id,
                'lowercaseId': lowercaseId,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'active': (status == 'TRADING'),
                'contract': False,
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(amountPrecision)),
                    'price': self.parse_number(self.parse_precision(pricePrecision)),
                    'base': self.parse_number(self.parse_precision(self.safe_string(market, 'baseAssetPrecision'))),
                    'quote': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(amountFilter, 'minQty'),
                        'max': self.safe_number(amountFilter, 'maxQty'),
                    },
                    'price': {
                        'min': self.safe_number(priceFilter, 'minPrice'),
                        'max': self.safe_number(priceFilter, 'maxPrice'),
                    },
                    'cost': {
                        'min': self.safe_number(amountFilter, 'minVal'),
                        'max': None,
                    },
                },
                'info': market,
            }
            result.append(entry)
        return result

    def parse_balance(self, response):
        result = {
            'info': response,
        }
        timestamp = self.safe_integer(response, 'updateTime')
        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
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        return self.safe_balance(result)

    async 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 bitrue api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        await self.load_markets()
        response = await self.v1PrivateGetAccount(params)
        #
        #     {
        #         "makerCommission":0,
        #         "takerCommission":0,
        #         "buyerCommission":0,
        #         "sellerCommission":0,
        #         "updateTime":null,
        #         "balances":[
        #             {"asset":"sbr","free":"0","locked":"0"},
        #             {"asset":"ksm","free":"0","locked":"0"},
        #             {"asset":"neo3s","free":"0","locked":"0"},
        #         ],
        #         "canTrade":false,
        #         "canWithdraw":false,
        #         "canDeposit":false
        #     }
        #
        return self.parse_balance(response)

    async 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 bitrue 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
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 100, max 1000, see https://github.com/Bitrue-exchange/bitrue-official-api-docs#order-book
        response = await self.v1PublicGetDepth(self.extend(request, params))
        #
        #     {
        #         "lastUpdateId":1635474910177,
        #         "bids":[
        #             ["61436.84","0.05",[]],
        #             ["61435.77","0.0124",[]],
        #             ["61434.88","0.012",[]],
        #         ],
        #         "asks":[
        #             ["61452.46","0.0001",[]],
        #             ["61452.47","0.0597",[]],
        #             ["61452.76","0.0713",[]],
        #         ]
        #     }
        #
        orderbook = self.parse_order_book(response, symbol)
        orderbook['nonce'] = self.safe_integer(response, 'lastUpdateId')
        return orderbook

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker
        #
        #     {
        #         "id":397945892,
        #         "last":"1.143411",
        #         "lowestAsk":"1.144223",
        #         "highestBid":"1.141696",
        #         "percentChange":"-0.001432",
        #         "baseVolume":"338287",
        #         "quoteVolume":"415013.244366",
        #         "isFrozen":"0",
        #         "high24hr":"1.370087",
        #         "low24hr":"1.370087",
        #     }
        #
        symbol = self.safe_symbol(None, market)
        last = self.safe_string(ticker, 'last')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'high': self.safe_string(ticker, 'high24hr'),
            'low': self.safe_string(ticker, 'low24hr'),
            'bid': self.safe_string(ticker, 'highestBid'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'lowestAsk'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': self.safe_string(ticker, 'percentChange'),
            'average': None,
            'baseVolume': self.safe_string(ticker, 'baseVolume'),
            'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
            'info': ticker,
        }, market)

    async 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 bitrue api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        uppercaseBaseId = self.safe_string_upper(market, 'baseId')
        uppercaseQuoteId = self.safe_string_upper(market, 'quoteId')
        request = {
            'currency': uppercaseQuoteId,
            'command': 'returnTicker',
        }
        response = await self.klinePublicGetPublicCurrencyJson(self.extend(request, params))
        #
        #     {
        #         "code":"200",
        #         "msg":"success",
        #         "data":{
        #             "DODO3S_USDT":{
        #                 "id":397945892,
        #                 "last":"1.143411",
        #                 "lowestAsk":"1.144223",
        #                 "highestBid":"1.141696",
        #                 "percentChange":"-0.001432",
        #                 "baseVolume":"338287",
        #                 "quoteVolume":"415013.244366",
        #                 "isFrozen":"0",
        #                 "high24hr":"1.370087",
        #                 "low24hr":"1.370087"
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        id = uppercaseBaseId + '_' + uppercaseQuoteId
        ticker = self.safe_value(data, id)
        if ticker is None:
            raise ExchangeError(self.id + ' fetchTicker() could not find the ticker for ' + market['symbol'])
        return self.parse_ticker(ticker, market)

    async 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 bitrue api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'scale': self.timeframes[timeframe],
        }
        if limit is not None:
            request['limit'] = limit
        response = await self.v1PublicGetMarketKline(self.extend(request, params))
        #
        #       {
        #           "symbol":"BTCUSDT",
        #           "scale":"KLINE_1MIN",
        #           "data":[
        #                {
        #                   "i":"1660825020",
        #                   "a":"93458.778",
        #                   "v":"3.9774",
        #                   "c":"23494.99",
        #                   "h":"23509.63",
        #                   "l":"23491.93",
        #                   "o":"23508.34"
        #                }
        #           ]
        #       }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #      {
        #         "i":"1660825020",
        #         "a":"93458.778",
        #         "v":"3.9774",
        #         "c":"23494.99",
        #         "h":"23509.63",
        #         "l":"23491.93",
        #         "o":"23508.34"
        #      }
        #
        return [
            self.safe_timestamp(ohlcv, 'i'),
            self.safe_number(ohlcv, 'o'),
            self.safe_number(ohlcv, 'h'),
            self.safe_number(ohlcv, 'l'),
            self.safe_number(ohlcv, 'c'),
            self.safe_number(ohlcv, 'v'),
        ]

    async def fetch_bids_asks(self, symbols=None, params={}):
        """
        fetches the bid and ask price and volume for multiple markets
        :param [str]|None symbols: unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        defaultType = self.safe_string_2(self.options, 'fetchBidsAsks', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        query = self.omit(params, 'type')
        method = None
        if type == 'future':
            method = 'fapiPublicGetTickerBookTicker'
        elif type == 'delivery':
            method = 'dapiPublicGetTickerBookTicker'
        else:
            method = 'publicGetTickerBookTicker'
        response = await getattr(self, method)(query)
        return self.parse_tickers(response, symbols)

    async def fetch_tickers(self, symbols=None, params={}):
        """
        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 bitrue api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        await self.load_markets()
        request = {
            'command': 'returnTicker',
        }
        response = await self.klinePublicGetPublicJson(self.extend(request, params))
        #
        #     {
        #         "code":"200",
        #         "msg":"success",
        #         "data":{
        #             "DODO3S_USDT":{
        #                 "id":397945892,
        #                 "last":"1.143411",
        #                 "lowestAsk":"1.144223",
        #                 "highestBid":"1.141696",
        #                 "percentChange":"-0.001432",
        #                 "baseVolume":"338287",
        #                 "quoteVolume":"415013.244366",
        #                 "isFrozen":"0",
        #                 "high24hr":"1.370087",
        #                 "low24hr":"1.370087"
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        # the exchange returns market ids with an underscore from the tickers endpoint
        # the market ids do not have an underscore, so it has to be removed
        # https://github.com/ccxt/ccxt/issues/13856
        tickers = {}
        marketIds = list(data.keys())
        for i in range(0, len(marketIds)):
            marketId = marketIds[i].replace('_', '')
            tickers[marketId] = data[marketIds[i]]
        return self.parse_tickers(tickers, symbols)

    def parse_trade(self, trade, market=None):
        #
        # aggregate trades
        #
        #     {
        #         "a": 26129,         # Aggregate tradeId
        #         "p": "0.01633102",  # Price
        #         "q": "4.70443515",  # Quantity
        #         "f": 27781,         # First tradeId
        #         "l": 27781,         # Last tradeId
        #         "T": 1498793709153,  # Timestamp
        #         "m": True,          # Was the buyer the maker?
        #         "M": True           # Was the trade the best price match?
        #     }
        #
        # recent public trades and old public trades
        #
        #     {
        #         "id": 28457,
        #         "price": "4.00000100",
        #         "qty": "12.00000000",
        #         "time": 1499865549590,
        #         "isBuyerMaker": True,
        #         "isBestMatch": True
        #     }
        #
        # private trades
        #
        #     {
        #         "symbol":"USDCUSDT",
        #         "id":20725156,
        #         "orderId":2880918576,
        #         "origClientOrderId":null,
        #         "price":"0.9996000000000000",
        #         "qty":"100.0000000000000000",
        #         "commission":null,
        #         "commissionAssert":null,
        #         "time":1635558511000,
        #         "isBuyer":false,
        #         "isMaker":false,
        #         "isBestMatch":true
        #     }
        #
        timestamp = self.safe_integer_2(trade, 'T', 'time')
        priceString = self.safe_string_2(trade, 'p', 'price')
        amountString = self.safe_string_2(trade, 'q', 'qty')
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        id = self.safe_string_2(trade, 't', 'a')
        id = self.safe_string_2(trade, 'id', 'tradeId', id)
        side = None
        orderId = self.safe_string(trade, 'orderId')
        if 'm' in trade:
            side = 'sell' if trade['m'] else 'buy'  # self is reversed intentionally
        elif 'isBuyerMaker' in trade:
            side = 'sell' if trade['isBuyerMaker'] else 'buy'
        elif 'side' in trade:
            side = self.safe_string_lower(trade, 'side')
        else:
            if 'isBuyer' in trade:
                side = 'buy' if trade['isBuyer'] else 'sell'  # self is a True side
        fee = None
        if 'commission' in trade:
            fee = {
                'cost': self.safe_string(trade, 'commission'),
                'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAssert')),
            }
        takerOrMaker = None
        if 'isMaker' in trade:
            takerOrMaker = 'maker' if trade['isMaker'] else 'taker'
        if 'maker' in trade:
            takerOrMaker = 'maker' if trade['maker'] else 'taker'
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
        }, market)

    async 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 bitrue api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            # 'limit': 100,  # default 100, max = 1000
        }
        method = self.safe_string(self.options, 'fetchTradesMethod', 'v1PublicGetAggTrades')
        if limit is not None:
            request['limit'] = limit  # default 100, max 1000
        #
        # Caveats:
        # - default limit(500) applies only if no other parameters set, trades up
        #   to the maximum limit may be returned to satisfy other parameters
        # - if both limit and time window is set and time window contains more
        #   trades than the limit then the last trades from the window are returned
        # - 'tradeId' accepted and returned by self method is "aggregate" trade id
        #   which is different from actual trade id
        # - setting both fromId and time window results in error
        response = await getattr(self, method)(self.extend(request, params))
        #
        # aggregate trades
        #
        #     [
        #         {
        #             "a": 26129,         # Aggregate tradeId
        #             "p": "0.01633102",  # Price
        #             "q": "4.70443515",  # Quantity
        #             "f": 27781,         # First tradeId
        #             "l": 27781,         # Last tradeId
        #             "T": 1498793709153,  # Timestamp
        #             "m": True,          # Was the buyer the maker?
        #             "M": True           # Was the trade the best price match?
        #         }
        #     ]
        #
        # recent public trades and historical public trades
        #
        #     [
        #         {
        #             "id": 28457,
        #             "price": "4.00000100",
        #             "qty": "12.00000000",
        #             "time": 1499865549590,
        #             "isBuyerMaker": True,
        #             "isBestMatch": True
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

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

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "symbol":"USDCUSDT",
        #         "orderId":2878854881,
        #         "clientOrderId":"",
        #         "transactTime":1635551031276
        #     }
        #
        # fetchOpenOrders
        #
        #     {
        #         "symbol":"USDCUSDT",
        #         "orderId":"2878854881",
        #         "clientOrderId":"",
        #         "price":"1.1000000000000000",
        #         "origQty":"100.0000000000000000",
        #         "executedQty":"0.0000000000000000",
        #         "cummulativeQuoteQty":"0.0000000000000000",
        #         "status":"NEW",
        #         "timeInForce":"",
        #         "type":"LIMIT",
        #         "side":"SELL",
        #         "stopPrice":"",
        #         "icebergQty":"",
        #         "time":1635551031000,
        #         "updateTime":1635551031000,
        #         "isWorking":false
        #     }
        #
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        filled = self.safe_string(order, 'executedQty')
        timestamp = None
        lastTradeTimestamp = None
        if 'time' in order:
            timestamp = self.safe_integer(order, 'time')
        elif 'transactTime' in order:
            timestamp = self.safe_integer(order, 'transactTime')
        elif 'updateTime' in order:
            if status == 'open':
                if Precise.string_gt(filled, '0'):
                    lastTradeTimestamp = self.safe_integer(order, 'updateTime')
                else:
                    timestamp = self.safe_integer(order, 'updateTime')
        average = self.safe_string(order, 'avgPrice')
        price = self.safe_string(order, 'price')
        amount = self.safe_string(order, 'origQty')
        # - Spot/Margin market: cummulativeQuoteQty
        # - Futures market: cumQuote.
        #   Note self is not the actual cost, since Binance futures uses leverage to calculate margins.
        cost = self.safe_string_2(order, 'cummulativeQuoteQty', 'cumQuote')
        id = self.safe_string(order, 'orderId')
        type = self.safe_string_lower(order, 'type')
        side = self.safe_string_lower(order, 'side')
        fills = self.safe_value(order, 'fills', [])
        clientOrderId = self.safe_string(order, 'clientOrderId')
        timeInForce = self.safe_string(order, 'timeInForce')
        postOnly = (type == 'limit_maker') or (timeInForce == 'GTX')
        if type == 'limit_maker':
            type = 'limit'
        stopPriceString = self.safe_string(order, 'stopPrice')
        stopPrice = self.parse_number(self.omit_zero(stopPriceString))
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': None,
            'trades': fills,
        }, market)

    async 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 bitrue api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        uppercaseType = type.upper()
        validOrderTypes = self.safe_value(market['info'], 'orderTypes')
        if not self.in_array(uppercaseType, validOrderTypes):
            raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type in market ' + symbol)
        request = {
            'symbol': market['id'],
            'side': side.upper(),
            'type': uppercaseType,
            # 'timeInForce': '',
            'quantity': self.amount_to_precision(symbol, amount),
            # 'price': self.price_to_precision(symbol, price),
            # 'newClientOrderId': clientOrderId,  # automatically generated if not sent
            # 'stopPrice': self.price_to_precision(symbol, 'stopPrice'),
            # 'icebergQty': self.amount_to_precision(symbol, icebergQty),
        }
        clientOrderId = self.safe_string_2(params, 'newClientOrderId', 'clientOrderId')
        if clientOrderId is not None:
            params = self.omit(params, ['newClientOrderId', 'clientOrderId'])
            request['newClientOrderId'] = clientOrderId
        if uppercaseType == 'LIMIT':
            if price is None:
                raise InvalidOrder(self.id + ' createOrder() requires a price argument')
            request['price'] = self.price_to_precision(symbol, price)
        stopPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
        if stopPrice is not None:
            params = self.omit(params, ['triggerPrice', 'stopPrice'])
            request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
        response = await self.v1PrivatePostOrder(self.extend(request, params))
        #
        #     {
        #         "symbol":"USDCUSDT",
        #         "orderId":2878854881,
        #         "clientOrderId":"",
        #         "transactTime":1635551031276
        #     }
        #
        return self.parse_order(response, market)

    async def fetch_order(self, id, symbol=None, params={}):
        """
        fetches information on an order made by the user
        :param str symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        clientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
        if clientOrderId is not None:
            request['origClientOrderId'] = clientOrderId
        else:
            request['orderId'] = id
        query = self.omit(params, ['type', 'clientOrderId', 'origClientOrderId'])
        response = await self.v1PrivateGetOrder(self.extend(request, query))
        return self.parse_order(response, market)

    async 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 bitrue 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')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            # 'orderId': 123445,  # long
            # 'startTime': since,
            # 'endTime': self.milliseconds(),
            # 'limit': limit,  # default 100, max 1000
        }
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit  # default 100, max 1000
        response = await self.v1PrivateGetAllOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "symbol": "LTCBTC",
        #             "orderId": 1,
        #             "clientOrderId": "myOrder1",
        #             "price": "0.1",
        #             "origQty": "1.0",
        #             "executedQty": "0.0",
        #             "cummulativeQuoteQty": "0.0",
        #             "status": "NEW",
        #             "timeInForce": "GTC",
        #             "type": "LIMIT",
        #             "side": "BUY",
        #             "stopPrice": "0.0",
        #             "icebergQty": "0.0",
        #             "time": 1499827319559,
        #             "updateTime": 1499827319559,
        #             "isWorking": True
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async 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 bitrue 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')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = await self.v1PrivateGetOpenOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "symbol":"USDCUSDT",
        #             "orderId":"2878854881",
        #             "clientOrderId":"",
        #             "price":"1.1000000000000000",
        #             "origQty":"100.0000000000000000",
        #             "executedQty":"0.0000000000000000",
        #             "cummulativeQuoteQty":"0.0000000000000000",
        #             "status":"NEW",
        #             "timeInForce":"",
        #             "type":"LIMIT",
        #             "side":"SELL",
        #             "stopPrice":"",
        #             "icebergQty":"",
        #             "time":1635551031000,
        #             "updateTime":1635551031000,
        #             "isWorking":false
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        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_2(params, 'origClientOrderId', 'clientOrderId')
        request = {
            'symbol': market['id'],
            # 'orderId': id,
            # 'origClientOrderId': id,
            # 'newClientOrderId': id,
        }
        if origClientOrderId is None:
            request['orderId'] = id
        else:
            request['origClientOrderId'] = origClientOrderId
        query = self.omit(params, ['type', 'origClientOrderId', 'clientOrderId'])
        response = await self.v1PrivateDeleteOrder(self.extend(request, query))
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "origClientOrderId": "myOrder1",
        #         "orderId": 1,
        #         "clientOrderId": "cancelMyOrder1"
        #     }
        #
        return self.parse_order(response, market)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all trades made by the user
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        method = self.safe_string(self.options, 'fetchMyTradesMethod', 'v2PrivateGetMyTrades')
        if (symbol is None) and (method == 'v2PrivateGetMyTrades'):
            raise ArgumentsRequired(self.id + ' v2PrivateGetMyTrades() requires a symbol argument')
        await self.load_markets()
        request = {
            # 'symbol': market['id'],
            # 'startTime': since,
            # 'endTime': self.milliseconds(),
            # 'fromId': 12345,  # trade id to fetch from, most recent trades by default
            # 'limit': limit,  # default 100, max 1000
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     [
        #         {
        #             "symbol":"USDCUSDT",
        #             "id":20725156,
        #             "orderId":2880918576,
        #             "origClientOrderId":null,
        #             "price":"0.9996000000000000",
        #             "qty":"100.0000000000000000",
        #             "commission":null,
        #             "commissionAssert":null,
        #             "time":1635558511000,
        #             "isBuyer":false,
        #             "isMaker":false,
        #             "isBestMatch":true
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async 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 bitrue 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 a code argument')
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
            'status': 1,  # 0 init, 1 finished, default 0
            # 'offset': 0,
            # 'limit': limit,  # default 10, max 1000
            # 'startTime': since,
            # 'endTime': self.milliseconds(),
        }
        if since is not None:
            request['startTime'] = since
            # request['endTime'] = self.sum(since, 7776000000)
        if limit is not None:
            request['limit'] = limit
        response = await self.v1PrivateGetDepositHistory(self.extend(request, params))
        #
        #     {
        #         "code":200,
        #         "msg":"succ",
        #         "data":[
        #             {
        #                 "id":2659137,
        #                 "symbol":"USDC",
        #                 "amount":"200.0000000000000000",
        #                 "fee":"0.0E-15",
        #                 "createdAt":1635503169000,
        #                 "updatedAt":1635503202000,
        #                 "addressFrom":"0x2faf487a4414fe77e2327f0bf4ae2a264a776ad2",
        #                 "addressTo":"0x190ceccb1f8bfbec1749180f0ba8922b488d865b",
        #                 "txid":"0x9970aec41099ac385568859517308707bc7d716df8dabae7b52f5b17351c3ed0",
        #                 "confirmations":5,
        #                 "status":0,
        #                 "tagType":null,
        #             },
        #             {
        #                 "id":2659137,
        #                 "symbol": "XRP",
        #                 "amount": "20.0000000000000000",
        #                 "fee": "0.0E-15",
        #                 "createdAt": 1544669393000,
        #                 "updatedAt": 1544669413000,
        #                 "addressFrom": "",
        #                 "addressTo": "raLPjTYeGezfdb6crXZzcC8RkLBEwbBHJ5_18113641",
        #                 "txid": "515B23E1F9864D3AF7F5B4C4FCBED784BAE861854FAB95F4031922B6AAEFC7AC",
        #                 "confirmations": 7,
        #                 "status": 1,
        #                 "tagType": "Tag"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit)

    async 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 bitrue 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 a code argument')
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
            'status': 5,  # 0 init, 5 finished, 6 canceled, default 0
            # 'offset': 0,
            # 'limit': limit,  # default 10, max 1000
            # 'startTime': since,
            # 'endTime': self.milliseconds(),
        }
        if since is not None:
            request['startTime'] = since
            # request['endTime'] = self.sum(since, 7776000000)
        if limit is not None:
            request['limit'] = limit
        response = await self.v1PrivateGetWithdrawHistory(self.extend(request, params))
        #
        #     {
        #         "code": 200,
        #         "msg": "succ",
        #         "data": {
        #             "msg": null,
        #             "amount": 1000,
        #             "fee": 1,
        #             "ctime": null,
        #             "coin": "usdt_erc20",
        #             "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.parse_transactions(data, currency)

    def parse_transaction_status_by_type(self, status, type=None):
        statusesByType = {
            'deposit': {
                '0': 'pending',
                '1': 'ok',
            },
            'withdrawal': {
                '0': 'pending',  # Email Sent
                '5': 'ok',  # Failure
                '6': 'canceled',
            },
        }
        statuses = self.safe_value(statusesByType, type, {})
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #         "symbol": "XRP",
        #         "amount": "261.3361000000000000",
        #         "fee": "0.0E-15",
        #         "createdAt": 1548816979000,
        #         "updatedAt": 1548816999000,
        #         "addressFrom": "",
        #         "addressTo": "raLPjTYeGezfdb6crXZzcC8RkLBEwbBHJ5_18113641",
        #         "txid": "86D6EB68A7A28938BCE06BD348F8C07DEF500C5F7FE92069EF8C0551CE0F2C7D",
        #         "confirmations": 8,
        #         "status": 1,
        #         "tagType": "Tag"
        #     },
        #     {
        #         "symbol": "XRP",
        #         "amount": "20.0000000000000000",
        #         "fee": "0.0E-15",
        #         "createdAt": 1544669393000,
        #         "updatedAt": 1544669413000,
        #         "addressFrom": "",
        #         "addressTo": "raLPjTYeGezfdb6crXZzcC8RkLBEwbBHJ5_18113641",
        #         "txid": "515B23E1F9864D3AF7F5B4C4FCBED784BAE861854FAB95F4031922B6AAEFC7AC",
        #         "confirmations": 7,
        #         "status": 1,
        #         "tagType": "Tag"
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "id": 183745,
        #         "symbol": "usdt_erc20",
        #         "amount": "8.4000000000000000",
        #         "fee": "1.6000000000000000",
        #         "payAmount": "0.0000000000000000",
        #         "createdAt": 1595336441000,
        #         "updatedAt": 1595336576000,
        #         "addressFrom": "",
        #         "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83",
        #         "txid": "",
        #         "confirmations": 0,
        #         "status": 6,
        #         "tagType": null
        #     }
        #
        # withdraw
        #
        #     {
        #         "msg": null,
        #         "amount": 1000,
        #         "fee": 1,
        #         "ctime": null,
        #         "coin": "usdt_erc20",
        #         "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83"
        #     }
        #
        id = self.safe_string(transaction, 'id')
        tagType = self.safe_string(transaction, 'tagType')
        addressTo = self.safe_string(transaction, 'addressTo')
        addressFrom = self.safe_string(transaction, 'addressFrom')
        tagTo = None
        tagFrom = None
        if tagType is not None:
            if addressTo is not None:
                parts = addressTo.split('_')
                addressTo = self.safe_string(parts, 0)
                tagTo = self.safe_string(parts, 1)
            if addressFrom is not None:
                parts = addressFrom.split('_')
                addressFrom = self.safe_string(parts, 0)
                tagFrom = self.safe_string(parts, 1)
        txid = self.safe_string(transaction, 'txid')
        timestamp = self.safe_integer(transaction, 'createdAt')
        updated = self.safe_integer(transaction, 'updatedAt')
        payAmount = ('payAmount' in transaction)
        ctime = ('ctime' in transaction)
        type = 'withdrawal' if (payAmount or ctime) else 'deposit'
        status = self.parse_transaction_status_by_type(self.safe_string(transaction, 'status'), type)
        amount = self.safe_number(transaction, 'amount')
        network = None
        currencyId = self.safe_string(transaction, 'symbol')
        if currencyId is not None:
            parts = currencyId.split('_')
            currencyId = self.safe_string(parts, 0)
            networkId = self.safe_string(parts, 1)
            if networkId is not None:
                network = networkId.upper()
        code = self.safe_currency_code(currencyId, currency)
        feeCost = self.safe_number(transaction, 'fee')
        fee = None
        if feeCost is not None:
            fee = {'currency': code, 'cost': feeCost}
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': network,
            'address': addressTo,
            'addressTo': addressTo,
            'addressFrom': addressFrom,
            'tag': tagTo,
            'tagTo': tagTo,
            'tagFrom': tagFrom,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'internal': False,
            'fee': fee,
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        """
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str|None tag:
        :param dict params: extra parameters specific to the bitrue api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        chainName = self.safe_string(params, 'chainName')
        if chainName is None:
            networks = self.safe_value(currency, 'networks', {})
            network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
            networkEntry = self.safe_value(networks, network, {})
            chainName = self.safe_string(networkEntry, 'id')  # handle ERC20>ETH alias
            if chainName is None:
                raise ArgumentsRequired(self.id + ' withdraw() requires a network parameter or a chainName parameter')
            params = self.omit(params, 'network')
        request = {
            'coin': currency['id'].upper(),
            'amount': amount,
            'addressTo': address,
            'chainName': chainName,  # 'ERC20', 'TRC20', 'SOL'
            # 'addressMark': '',  # mark of address
            # 'addrType': '',  # type of address
            # 'tag': tag,
        }
        if tag is not None:
            request['tag'] = tag
        response = await self.v1PrivatePostWithdrawCommit(self.extend(request, params))
        #     {id: '9a67628b16ba4988ae20d329333f16bc'}
        return self.parse_transaction(response, currency)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        version, access = api
        url = self.urls['api'][version] + '/' + self.implode_params(path, params)
        params = self.omit(params, self.extract_params(path))
        if access == 'private':
            self.check_required_credentials()
            recvWindow = self.safe_integer(self.options, 'recvWindow', 5000)
            query = self.urlencode(self.extend({
                'timestamp': self.nonce(),
                'recvWindow': recvWindow,
            }, params))
            signature = self.hmac(self.encode(query), self.encode(self.secret))
            query += '&' + 'signature=' + signature
            headers = {
                'X-MBX-APIKEY': self.apiKey,
            }
            if (method == 'GET') or (method == 'DELETE'):
                url += '?' + query
            else:
                body = query
                headers['Content-Type'] = 'application/x-www-form-urlencoded'
        else:
            if params:
                url += '?' + self.urlencode(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

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

    def calculate_rate_limiter_cost(self, api, method, path, params, config={}, context={}):
        if ('noSymbol' in config) and not ('symbol' in params):
            return config['noSymbol']
        elif ('byLimit' in config) and ('limit' in params):
            limit = params['limit']
            byLimit = config['byLimit']
            for i in range(0, len(byLimit)):
                entry = byLimit[i]
                if limit <= entry[0]:
                    return entry[1]
        return self.safe_value(config, 'cost', 1)
