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

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

from ccxt_zagrava.base.exchange import Exchange
from ccxt_zagrava.abstract.bitrue import ImplicitAPI
import hashlib
import json
from ccxt_zagrava.base.types import Balances, Currencies, Currency, Int, MarginModification, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction, TransferEntry, TransferEntries
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import AccountSuspended
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import BadSymbol
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.errors import OrderImmediatelyFillable
from ccxt_zagrava.base.errors import NotSupported
from ccxt_zagrava.base.errors import DDoSProtection
from ccxt_zagrava.base.errors import RateLimitExceeded
from ccxt_zagrava.base.errors import ExchangeNotAvailable
from ccxt_zagrava.base.errors import OnMaintenance
from ccxt_zagrava.base.errors import InvalidNonce
from ccxt_zagrava.base.decimal_to_precision import TRUNCATE
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class bitrue(Exchange, ImplicitAPI):

    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',
            'pro': True,
            # new metainfo interface
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': True,
                'future': False,
                'option': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'fetchBalance': True,
                'fetchBidsAsks': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchClosedOrders': True,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': False,
                'fetchDeposits': True,
                'fetchDepositsWithdrawals': False,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': True,
                'fetchFundingRate': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                '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': True,
                'fetchWithdrawals': True,
                'setLeverage': True,
                'setMargin': True,
                'transfer': True,
                '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': {
                    'spot': 'https://www.bitrue.com/api',
                    'fapi': 'https://fapi.bitrue.com/fapi',
                    'dapi': 'https://fapi.bitrue.com/dapi',
                    'kline': 'https://www.bitrue.com/kline-api',
                },
                'www': 'https://www.bitrue.com',
                'referral': 'https://www.bitrue.com/affiliate/landing?cn=600000&inviteCode=EZWETQE',
                'doc': [
                    'https://github.com/Bitrue-exchange/bitrue-official-api-docs',
                    'https://www.bitrue.com/api-docs',
                ],
                'fees': 'https://bitrue.zendesk.com/hc/en-001/articles/4405479952537',
            },
            'api': {
                'spot': {
                    '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,
                            },
                        },
                    },
                },
                'fapi': {
                    'v1': {
                        'public': {
                            'get': {
                                'ping': 1,
                                'time': 1,
                                'contracts': 1,
                                'depth': 1,
                                'ticker': 1,
                                'klines': 1,
                            },
                        },
                    },
                    'v2': {
                        'private': {
                            'get': {
                                'myTrades': 1,
                                'openOrders': 1,
                                'order': 1,
                                'account': 1,
                                'leverageBracket': 1,
                                'commissionRate': 1,
                                'futures_transfer_history': 1,
                                'forceOrdersHistory': 1,
                            },
                            'post': {
                                'positionMargin': 1,
                                'level_edit': 1,
                                'cancel': 1,
                                'order': 1,
                                'allOpenOrders': 1,
                                'futures_transfer': 1,
                            },
                        },
                    },
                },
                'dapi': {
                    'v1': {
                        'public': {
                            'get': {
                                'ping': 1,
                                'time': 1,
                                'contracts': 1,
                                'depth': 1,
                                'ticker': 1,
                                'klines': 1,
                            },
                        },
                    },
                    'v2': {
                        'private': {
                            'get': {
                                'myTrades': 1,
                                'openOrders': 1,
                                'order': 1,
                                'account': 1,
                                'leverageBracket': 1,
                                'commissionRate': 1,
                                'futures_transfer_history': 1,
                                'forceOrdersHistory': 1,
                            },
                            'post': {
                                'positionMargin': 1,
                                'level_edit': 1,
                                'cancel': 1,
                                'order': 1,
                                'allOpenOrders': 1,
                                'futures_transfer': 1,
                            },
                        },
                    },
                },
            },
            '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': {
                'createMarketBuyOrderRequiresPrice': True,
                'fetchMarkets': [
                    'spot',
                    'linear',
                    'inverse',
                ],
                # 'fetchTradesMethod': 'publicGetAggTrades',  # publicGetTrades, publicGetHistoricalTrades
                'fetchMyTradesMethod': 'v2PrivateGetMyTrades',  # spotV1PrivateGetMyTrades
                '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': {
                    'ERC20': 'ETH',
                    'TRC20': 'TRX',
                },
                'defaultType': 'spot',
                'timeframes': {
                    'spot': {
                        '1m': '1m',
                        '5m': '5m',
                        '15m': '15m',
                        '30m': '30m',
                        '1h': '1H',
                        '2h': '2H',
                        '4h': '4H',
                        '12h': '12H',
                        '1d': '1D',
                        '1w': '1W',
                    },
                    'future': {
                        '1m': '1min',
                        '5m': '5min',
                        '15m': '15min',
                        '30m': '30min',
                        '1h': '1h',
                        '1d': '1day',
                        '1w': '1week',
                        '1M': '1month',
                    },
                },
                'accountsByType': {
                    'spot': 'wallet',
                    'future': 'contract',
                    'swap': 'contract',
                    'funding': 'wallet',
                    'fund': 'wallet',
                    'contract': 'contract',
                },
            },
            '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  # {"code":-1102,"msg":"timestamp IllegalArgumentException.","data":null}
                    '-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.
                    '-1166': InvalidOrder,  # {"code":"-1166","msg":"The leverage value of the order is inconsistent with the user contract configuration 5","data":null}
                    '-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
                    '-1160': InvalidOrder,  # {"code":"-1160","msg":"Minimum order amount 10","data":null}
                    '-1156': InvalidOrder,  # {"code":"-1156","msg":"The number of closed positions exceeds the total number of positions","data":null}
                    '-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."
                    '-2017': InsufficientFunds,  # {code":"-2017","msg":"Insufficient balance","data":null}
                    '-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 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']

    def fetch_status(self, params={}):
        """
        the latest known information on the availability of the exchange API
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#test-connectivity
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `status structure <https://docs.ccxt_zagrava.com/#/?id=exchange-status-structure>`
        """
        response = self.spotV1PublicGetPing(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,
        }

    def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#check-server-time
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = self.spotV1PublicGetTime(params)
        #
        #     {
        #         "serverTime":1635467280514
        #     }
        #
        return self.safe_integer(response, 'serverTime')

    def safe_network(self, networkId):
        uppercaseNetworkId = networkId.upper()
        networksById: dict = {
            '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)

    def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.spotV1PublicGetExchangeInfo(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: dict = {}
        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)
            deposit = None
            withdraw = None
            minWithdrawString = None
            maxWithdrawString = None
            minWithdrawFeeString = None
            networkDetails = self.safe_value(currency, 'chainDetail', [])
            networks: dict = {}
            for j in range(0, len(networkDetails)):
                entry = networkDetails[j]
                networkId = self.safe_string(entry, 'chain')
                network = self.network_id_to_code(networkId, code)
                enableDeposit = self.safe_value(entry, 'enableDeposit')
                deposit = enableDeposit if (enableDeposit) else deposit
                enableWithdraw = self.safe_value(entry, 'enableWithdraw')
                withdraw = enableWithdraw if (enableWithdraw) else withdraw
                networkWithdrawFeeString = self.safe_string(entry, 'withdrawFee')
                if networkWithdrawFeeString is not None:
                    minWithdrawFeeString = networkWithdrawFeeString if (minWithdrawFeeString is None) else Precise.string_min(networkWithdrawFeeString, minWithdrawFeeString)
                networkMinWithdrawString = self.safe_string(entry, 'minWithdraw')
                if networkMinWithdrawString is not None:
                    minWithdrawString = networkMinWithdrawString if (minWithdrawString is None) else Precise.string_min(networkMinWithdrawString, minWithdrawString)
                networkMaxWithdrawString = self.safe_string(entry, 'maxWithdraw')
                if networkMaxWithdrawString is not None:
                    maxWithdrawString = networkMaxWithdrawString if (maxWithdrawString is None) else Precise.string_max(networkMaxWithdrawString, maxWithdrawString)
                networks[network] = {
                    'info': entry,
                    'id': networkId,
                    'network': network,
                    'deposit': enableDeposit,
                    'withdraw': enableWithdraw,
                    'active': enableDeposit and enableWithdraw,
                    'fee': self.parse_number(networkWithdrawFeeString),
                    'precision': None,
                    'limits': {
                        'withdraw': {
                            'min': self.parse_number(networkMinWithdrawString),
                            'max': self.parse_number(networkMaxWithdrawString),
                        },
                    },
                }
            result[code] = {
                'id': id,
                'name': name,
                'code': code,
                'precision': None,
                'info': currency,
                'active': deposit and withdraw,
                'deposit': deposit,
                'withdraw': withdraw,
                'networks': networks,
                'fee': self.parse_number(minWithdrawFeeString),
                # 'fees': fees,
                'limits': {
                    'withdraw': {
                        'min': self.parse_number(minWithdrawString),
                        'max': self.parse_number(maxWithdrawString),
                    },
                },
            }
        return result

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for bitrue
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#exchangeInfo_endpoint
        :see: https://www.bitrue.com/api-docs#current-open-contract
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#current-open-contract
        :param dict [params]: extra parameters specific to the exchange api endpoint
        :returns dict[]: an array of objects representing market data
        """
        promisesRaw = []
        fetchMarkets = self.safe_value(self.options, 'fetchMarkets', ['spot', 'linear', 'inverse'])
        for i in range(0, len(fetchMarkets)):
            marketType = fetchMarkets[i]
            if marketType == 'spot':
                promisesRaw.append(self.spotV1PublicGetExchangeInfo(params))
            elif marketType == 'linear':
                promisesRaw.append(self.fapiV1PublicGetContracts(params))
            elif marketType == 'inverse':
                promisesRaw.append(self.dapiV1PublicGetContracts(params))
            else:
                raise ExchangeError(self.id + ' fetchMarkets() self.options fetchMarkets "' + marketType + '" is not a supported market type')
        promises = promisesRaw
        spotMarkets = self.safe_value(self.safe_value(promises, 0), 'symbols', [])
        futureMarkets = self.safe_value(promises, 1)
        deliveryMarkets = self.safe_value(promises, 2)
        markets = spotMarkets
        markets = self.array_concat(markets, futureMarkets)
        markets = self.array_concat(markets, deliveryMarkets)
        #
        # spot
        #
        #     {
        #         "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",
        #             },
        #         ],
        #     }
        #
        # swap / delivery
        #
        #     [
        #         {
        #           "symbol": "H-HT-USDT",
        #           "pricePrecision": 8,
        #           "side": 1,
        #           "maxMarketVolume": 100000,
        #           "multiplier": 6,
        #           "minOrderVolume": 1,
        #           "maxMarketMoney": 10000000,
        #           "type": "H",  # E: perpetual contract, S: test contract, others are mixed contract
        #           "maxLimitVolume": 1000000,
        #           "maxValidOrder": 20,
        #           "multiplierCoin": "HT",
        #           "minOrderMoney": 0.001,
        #           "maxLimitMoney": 1000000,
        #           "status": 1
        #         }
        #     ]
        #
        if self.options['adjustForTimeDifference']:
            self.load_time_difference()
        return self.parse_markets(markets)

    def parse_market(self, market: dict) -> Market:
        id = self.safe_string(market, 'symbol')
        lowercaseId = self.safe_string_lower(market, 'symbol')
        side = self.safe_integer(market, 'side')  # 1 linear, 0 inverse, None spot
        type = None
        isLinear = None
        isInverse = None
        if side is None:
            type = 'spot'
        else:
            type = 'swap'
            isLinear = (side == 1)
            isInverse = (side == 0)
        isContract = (type != 'spot')
        baseId = self.safe_string(market, 'baseAsset')
        quoteId = self.safe_string(market, 'quoteAsset')
        settleId = None
        settle = None
        if isContract:
            symbolSplit = id.split('-')
            baseId = self.safe_string(symbolSplit, 1)
            quoteId = self.safe_string(symbolSplit, 2)
            if isLinear:
                settleId = quoteId
            else:
                settleId = baseId
            settle = self.safe_currency_code(settleId)
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        if settle is not None:
            symbol += ':' + settle
        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)
        multiplier = self.safe_string(market, 'multiplier')
        maxQuantity = self.safe_number(amountFilter, 'maxQty')
        if maxQuantity is None:
            maxQuantity = self.safe_number(market, 'maxValidOrder')
        minCost = self.safe_number(amountFilter, 'minVal')
        if minCost is None:
            minCost = self.safe_number(market, 'minOrderMoney')
        return {
            'id': id,
            'lowercaseId': lowercaseId,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'settle': settle,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': settleId,
            'type': type,
            'spot': (type == 'spot'),
            'margin': False,
            'swap': isContract,
            'future': False,
            'option': False,
            'active': (status == 'TRADING'),
            'contract': isContract,
            'linear': isLinear,
            'inverse': isInverse,
            'contractSize': self.parse_number(Precise.string_abs(multiplier)),
            '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)),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': self.safe_number(amountFilter, 'minQty'),
                    'max': maxQuantity,
                },
                'price': {
                    'min': self.safe_number(priceFilter, 'minPrice'),
                    'max': self.safe_number(priceFilter, 'maxPrice'),
                },
                'cost': {
                    'min': minCost,
                    'max': None,
                },
            },
            'created': None,
            'info': market,
        }

    def parse_balance(self, response) -> Balances:
        #
        # spot
        #
        #     {
        #         "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
        #     }
        #
        # swap
        #
        #     {
        #         "account":[
        #             {
        #                 "marginCoin":"USDT",
        #                 "coinPrecious":4,
        #                 "accountNormal":1010.4043400372839856,
        #                 "accountLock":2.9827889600000006,
        #                 "partPositionNormal":0,
        #                 "totalPositionNormal":0,
        #                 "achievedAmount":0,
        #                 "unrealizedAmount":0,
        #                 "totalMarginRate":0,
        #                 "totalEquity":1010.4043400372839856,
        #                 "partEquity":0,
        #                 "totalCost":0,
        #                 "sumMarginRate":0,
        #                 "sumOpenRealizedAmount":0,
        #                 "canUseTrialFund":0,
        #                 "sumMaintenanceMargin":null,
        #                 "futureModel":null,
        #                 "positionVos":[]
        #             }
        #         ]
        #     }
        #
        result: dict = {
            'info': response,
        }
        timestamp = self.safe_integer(response, 'updateTime')
        balances = self.safe_value_2(response, 'balances', 'account', [])
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string_2(balance, 'asset', 'marginCoin')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string_2(balance, 'free', 'accountNormal')
            account['used'] = self.safe_string_2(balance, 'locked', 'accountLock')
            result[code] = account
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        return self.safe_balance(result)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#account-information-user_data
        :see: https://www.bitrue.com/api-docs#account-information-v2-user_data-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#account-information-v2-user_data-hmac-sha256
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: 'future', 'delivery', 'spot', 'swap'
        :param str [params.subType]: 'linear', 'inverse'
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        self.load_markets()
        type = None
        type, params = self.handle_market_type_and_params('fetchBalance', None, params)
        subType = None
        subType, params = self.handle_sub_type_and_params('fetchBalance', None, params)
        response = None
        result = None
        if type == 'swap':
            if subType is not None and subType == 'inverse':
                response = self.dapiV2PrivateGetAccount(params)
                result = self.safe_value(response, 'data', {})
                #
                # {
                #         "code":"0",
                #         "msg":"Success",
                #         "data":{
                #             "account":[
                #                 {
                #                     "marginCoin":"USD",
                #                     "coinPrecious":4,
                #                     "accountNormal":1010.4043400372839856,
                #                     "accountLock":2.9827889600000006,
                #                     "partPositionNormal":0,
                #                     "totalPositionNormal":0,
                #                     "achievedAmount":0,
                #                     "unrealizedAmount":0,
                #                     "totalMarginRate":0,
                #                     "totalEquity":1010.4043400372839856,
                #                     "partEquity":0,
                #                     "totalCost":0,
                #                     "sumMarginRate":0,
                #                     "sumOpenRealizedAmount":0,
                #                     "canUseTrialFund":0,
                #                     "sumMaintenanceMargin":null,
                #                     "futureModel":null,
                #                     "positionVos":[]
                #                 }
                #             ]
                #         }
                #     }
                #
            else:
                response = self.fapiV2PrivateGetAccount(params)
                result = self.safe_value(response, 'data', {})
                #
                #     {
                #         "code":"0",
                #         "msg":"Success",
                #         "data":{
                #             "account":[
                #                 {
                #                     "marginCoin":"USDT",
                #                     "coinPrecious":4,
                #                     "accountNormal":1010.4043400372839856,
                #                     "accountLock":2.9827889600000006,
                #                     "partPositionNormal":0,
                #                     "totalPositionNormal":0,
                #                     "achievedAmount":0,
                #                     "unrealizedAmount":0,
                #                     "totalMarginRate":0,
                #                     "totalEquity":1010.4043400372839856,
                #                     "partEquity":0,
                #                     "totalCost":0,
                #                     "sumMarginRate":0,
                #                     "sumOpenRealizedAmount":0,
                #                     "canUseTrialFund":0,
                #                     "sumMaintenanceMargin":null,
                #                     "futureModel":null,
                #                     "positionVos":[]
                #                 }
                #             ]
                #         }
                #     }
                #
        else:
            response = self.spotV1PrivateGetAccount(params)
            result = response
            #
            #     {
            #         "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(result)

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#order-book
        :see: https://www.bitrue.com/api-docs#order-book
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#order-book
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        response = None
        if market['swap']:
            request: dict = {
                'contractName': market['id'],
            }
            if limit is not None:
                if limit > 100:
                    limit = 100
                request['limit'] = limit  # default 100, max 100, see https://www.bitrue.com/api-docs#order-book
            if market['linear']:
                response = self.fapiV1PublicGetDepth(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV1PublicGetDepth(self.extend(request, params))
        elif market['spot']:
            request: dict = {
                'symbol': market['id'],
            }
            if limit is not None:
                if limit > 1000:
                    limit = 1000
                request['limit'] = limit  # default 100, max 1000, see https://github.com/Bitrue-exchange/bitrue-official-api-docs#order-book
            response = self.spotV1PublicGetDepth(self.extend(request, params))
        else:
            raise NotSupported(self.id + ' fetchOrderBook only support spot & swap markets')
        #
        # spot
        #
        #     {
        #         "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",[]],
        #         ]
        #     }
        #
        # swap
        #
        #     {
        #         "asks": [[34916.5, 2582], [34916.6, 2193], [34916.7, 2629], [34916.8, 3478], [34916.9, 2718]],
        #         "bids": [[34916.4, 92065], [34916.3, 25703], [34916.2, 37259], [34916.1, 26446], [34916, 44456]],
        #         "time": 1699338305000
        #     }
        #
        timestamp = self.safe_integer(response, 'time')
        orderbook = self.parse_order_book(response, symbol, timestamp)
        orderbook['nonce'] = self.safe_integer(response, 'lastUpdateId')
        return orderbook

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        # fetchBidsAsks
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "bidPrice": "4.00000000",
        #         "bidQty": "431.00000000",
        #         "askPrice": "4.00000200",
        #         "askQty": "9.00000000"
        #     }
        #
        # fetchTicker
        #
        #     {
        #         "symbol": "BNBBTC",
        #         "priceChange": "0.000248",
        #         "priceChangePercent": "3.5500",
        #         "weightedAvgPrice": null,
        #         "prevClosePrice": null,
        #         "lastPrice": "0.007226",
        #         "lastQty": null,
        #         "bidPrice": "0.007208",
        #         "askPrice": "0.007240",
        #         "openPrice": "0.006978",
        #         "highPrice": "0.007295",
        #         "lowPrice": "0.006935",
        #         "volume": "11749.86",
        #         "quoteVolume": "84.1066211",
        #         "openTime": 0,
        #         "closeTime": 0,
        #         "firstId": 0,
        #         "lastId": 0,
        #         "count": 0
        #     }
        #
        symbol = self.safe_symbol(None, market)
        last = self.safe_string_2(ticker, 'lastPrice', 'last')
        timestamp = self.safe_integer(ticker, 'time')
        percentage = None
        if market['swap']:
            percentage = Precise.string_mul(self.safe_string(ticker, 'rose'), '100')
        else:
            percentage = self.safe_string(ticker, 'priceChangePercent')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string_2(ticker, 'highPrice', 'high'),
            'low': self.safe_string_2(ticker, 'lowPrice', 'low'),
            'bid': self.safe_string_2(ticker, 'bidPrice', 'buy'),
            'bidVolume': self.safe_string(ticker, 'bidQty'),
            'ask': self.safe_string_2(ticker, 'askPrice', 'sell'),
            'askVolume': self.safe_string(ticker, 'askQty'),
            'vwap': self.safe_string(ticker, 'weightedAvgPrice'),
            'open': self.safe_string(ticker, 'openPrice'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': self.safe_string(ticker, 'priceChange'),
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_string_2(ticker, 'volume', 'vol'),
            'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#24hr-ticker-price-change-statistics
        :see: https://www.bitrue.com/api-docs#ticker
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#ticker
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        response = None
        data = None
        if market['swap']:
            request: dict = {
                'contractName': market['id'],
            }
            if market['linear']:
                response = self.fapiV1PublicGetTicker(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV1PublicGetTicker(self.extend(request, params))
            data = response
        elif market['spot']:
            request: dict = {
                'symbol': market['id'],
            }
            response = self.spotV1PublicGetTicker24hr(self.extend(request, params))
            data = self.safe_value(response, 0, {})
        else:
            raise NotSupported(self.id + ' fetchTicker only support spot & swap markets')
        #
        # spot
        #
        #     [{
        #         symbol: 'BTCUSDT',
        #         priceChange: '105.20',
        #         priceChangePercent: '0.3000',
        #         weightedAvgPrice: null,
        #         prevClosePrice: null,
        #         lastPrice: '34905.21',
        #         lastQty: null,
        #         bidPrice: '34905.21',
        #         askPrice: '34905.22',
        #         openPrice: '34800.01',
        #         highPrice: '35276.33',
        #         lowPrice: '34787.51',
        #         volume: '12549.6481',
        #         quoteVolume: '439390492.917',
        #         openTime: '0',
        #         closeTime: '0',
        #         firstId: '0',
        #         lastId: '0',
        #         count: '0'
        #     }]
        #
        # swap
        #
        #     {
        #         "high": "35296",
        #         "vol": "779308354",
        #         "last": "34884.1",
        #         "low": "34806.7",
        #         "buy": 34883.9,
        #         "sell": 34884,
        #         "rose": "-0.0027957315",
        #         "time": 1699348013000
        #     }
        #
        return self.parse_ticker(data, market)

    def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#kline-data
        :see: https://www.bitrue.com/api-docs#kline-candlestick-data
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#kline-candlestick-data
        :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 [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        timeframes = self.safe_value(self.options, 'timeframes', {})
        response = None
        data = None
        if market['swap']:
            timeframesFuture = self.safe_value(timeframes, 'future', {})
            request: dict = {
                'contractName': market['id'],
                # 1min / 5min / 15min / 30min / 1h / 1day / 1week / 1month
                'interval': self.safe_string(timeframesFuture, timeframe, '1min'),
            }
            if limit is not None:
                request['limit'] = limit
            if market['linear']:
                response = self.fapiV1PublicGetKlines(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV1PublicGetKlines(self.extend(request, params))
            data = response
        elif market['spot']:
            timeframesSpot = self.safe_value(timeframes, 'spot', {})
            request: dict = {
                'symbol': market['id'],
                # 1m / 5m / 15m / 30m / 1H / 2H / 4H / 12H / 1D / 1W
                'scale': self.safe_string(timeframesSpot, timeframe, '1m'),
            }
            if limit is not None:
                request['limit'] = limit
            if since is not None:
                request['fromIdx'] = since
            response = self.spotV1PublicGetMarketKline(self.extend(request, params))
            data = self.safe_value(response, 'data', [])
        else:
            raise NotSupported(self.id + ' fetchOHLCV only support spot & swap markets')
        #
        # spot
        #
        #       {
        #           "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"
        #                }
        #           ]
        #       }
        #
        # swap
        #
        #     [
        #         {
        #           "high": "35360.7",
        #           "vol": "110288",
        #           "low": "35347.9",
        #           "idx": 1699411680000,
        #           "close": "35347.9",
        #           "open": "35349.4"
        #         }
        #     ]
        #
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        # spot
        #
        #      {
        #         "i":"1660825020",
        #         "a":"93458.778",
        #         "v":"3.9774",
        #         "c":"23494.99",
        #         "h":"23509.63",
        #         "l":"23491.93",
        #         "o":"23508.34"
        #      }
        #
        # swap
        #
        #     {
        #         "high": "35360.7",
        #         "vol": "110288",
        #         "low": "35347.9",
        #         "idx": 1699411680000,
        #         "close": "35347.9",
        #         "open": "35349.4"
        #     }
        #
        timestamp = self.safe_timestamp(ohlcv, 'i')
        if timestamp is None:
            timestamp = self.safe_integer(ohlcv, 'idx')
        return [
            timestamp,
            self.safe_number_2(ohlcv, 'o', 'open'),
            self.safe_number_2(ohlcv, 'h', 'high'),
            self.safe_number_2(ohlcv, 'l', 'low'),
            self.safe_number_2(ohlcv, 'c', 'close'),
            self.safe_number_2(ohlcv, 'v', 'vol'),
        ]

    def fetch_bids_asks(self, symbols: Strings = None, params={}):
        """
        fetches the bid and ask price and volume for multiple markets
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#symbol-order-book-ticker
        :see: https://www.bitrue.com/api-docs#ticker
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#ticker
        :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 exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols, None, False)
        first = self.safe_string(symbols, 0)
        market = self.market(first)
        response = None
        if market['swap']:
            request: dict = {
                'contractName': market['id'],
            }
            if market['linear']:
                response = self.fapiV1PublicGetTicker(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV1PublicGetTicker(self.extend(request, params))
        elif market['spot']:
            request: dict = {
                'symbol': market['id'],
            }
            response = self.spotV1PublicGetTickerBookTicker(self.extend(request, params))
        else:
            raise NotSupported(self.id + ' fetchBidsAsks only support spot & swap markets')
        #
        # spot
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "bidPrice": "4.00000000",
        #         "bidQty": "431.00000000",
        #         "askPrice": "4.00000200",
        #         "askQty": "9.00000000"
        #     }
        #
        # swap
        #
        #     {
        #         "high": "35296",
        #         "vol": "779308354",
        #         "last": "34884.1",
        #         "low": "34806.7",
        #         "buy": 34883.9,
        #         "sell": 34884,
        #         "rose": "-0.0027957315",
        #         "time": 1699348013000
        #     }
        #
        data: dict = {}
        data[market['id']] = response
        return self.parse_tickers(data, symbols)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#24hr-ticker-price-change-statistics
        :see: https://www.bitrue.com/api-docs#ticker
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#ticker
        :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 exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        response = None
        data = None
        request: dict = {}
        type = None
        if symbols is not None:
            first = self.safe_string(symbols, 0)
            market = self.market(first)
            if market['swap']:
                raise NotSupported(self.id + ' fetchTickers does not support swap markets, please use fetchTicker instead')
            elif market['spot']:
                response = self.spotV1PublicGetTicker24hr(self.extend(request, params))
                data = response
            else:
                raise NotSupported(self.id + ' fetchTickers only support spot & swap markets')
        else:
            type, params = self.handle_market_type_and_params('fetchTickers', None, params)
            if type != 'spot':
                raise NotSupported(self.id + ' fetchTickers only support spot when symbols are not proved')
            response = self.spotV1PublicGetTicker24hr(self.extend(request, params))
            data = response
        #
        # spot
        #
        #     [{
        #         symbol: 'BTCUSDT',
        #         priceChange: '105.20',
        #         priceChangePercent: '0.3000',
        #         weightedAvgPrice: null,
        #         prevClosePrice: null,
        #         lastPrice: '34905.21',
        #         lastQty: null,
        #         bidPrice: '34905.21',
        #         askPrice: '34905.22',
        #         openPrice: '34800.01',
        #         highPrice: '35276.33',
        #         lowPrice: '34787.51',
        #         volume: '12549.6481',
        #         quoteVolume: '439390492.917',
        #         openTime: '0',
        #         closeTime: '0',
        #         firstId: '0',
        #         lastId: '0',
        #         count: '0'
        #     }]
        #
        # swap
        #
        #     {
        #         "high": "35296",
        #         "vol": "779308354",
        #         "last": "34884.1",
        #         "low": "34806.7",
        #         "buy": 34883.9,
        #         "sell": 34884,
        #         "rose": "-0.0027957315",
        #         "time": 1699348013000
        #     }
        #
        # 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_zagrava/ccxt_zagrava/issues/13856
        tickers: dict = {}
        for i in range(0, len(data)):
            ticker = self.safe_value(data, i, {})
            market = self.market(self.safe_value(ticker, 'symbol'))
            tickers[market['id']] = ticker
        return self.parse_tickers(tickers, symbols)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades
        #
        #     {
        #         "id": 28457,
        #         "price": "4.00000100",
        #         "qty": "12.00000000",
        #         "time": 1499865549590,  # Actual timestamp of trade
        #         "isBuyerMaker": True,
        #         "isBestMatch": True
        #     }
        #
        # fetchTrades - spot
        #
        #     {
        #         "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
        #     }
        #
        # fetchTrades - future
        #
        #     {
        #         "tradeId":12,
        #         "price":0.9,
        #         "qty":1,
        #         "amount":9,
        #         "contractName":"E-SAND-USDT",
        #         "side":"BUY",
        #         "fee":"0.0018",
        #         "bidId":1558124009467904992,
        #         "askId":1558124043827644908,
        #         "bidUserId":10294,
        #         "askUserId":10467,
        #         "isBuyer":true,
        #         "isMaker":true,
        #         "ctime":1678426306000
        #     }
        #
        timestamp = self.safe_integer_2(trade, 'ctime', 'time')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'qty')
        marketId = self.safe_string_2(trade, 'symbol', 'contractName')
        symbol = self.safe_symbol(marketId, market)
        orderId = self.safe_string(trade, 'orderId')
        id = self.safe_string_2(trade, 'id', 'tradeId')
        side = None
        buyerMaker = self.safe_value(trade, 'isBuyerMaker')  # ignore "m" until Bitrue fixes api
        isBuyer = self.safe_value(trade, 'isBuyer')
        if buyerMaker is not None:
            side = 'sell' if buyerMaker else 'buy'
        if isBuyer is not None:
            side = 'buy' if isBuyer else 'sell'  # self is a True side
        fee = None
        if 'commission' in trade:
            fee = {
                'cost': self.safe_string_2(trade, 'commission', 'fee'),
                'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAssert')),
            }
        takerOrMaker = None
        isMaker = self.safe_value(trade, 'isMaker')
        if isMaker is not None:
            takerOrMaker = 'maker' if isMaker 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)

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#recent-trades-list
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        response = None
        if market['spot']:
            request: dict = {
                'symbol': market['id'],
                # 'limit': 100,  # default 100, max = 1000
            }
            if limit is not None:
                request['limit'] = limit  # default 100, max 1000
            response = self.spotV1PublicGetTrades(self.extend(request, params))
        else:
            raise NotSupported(self.id + ' fetchTrades only support spot markets')
        #
        # spot
        #
        #     [
        #         {
        #             "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: Str):
        statuses: dict = {
            'INIT': 'open',
            'PENDING_CREATE': 'open',
            '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: dict, market: Market = None) -> Order:
        #
        # createOrder - spot
        #
        #     {
        #         "symbol":"USDCUSDT",
        #         "orderId":2878854881,
        #         "clientOrderId":"",
        #         "transactTime":1635551031276
        #     }
        #
        # createOrder - future
        #
        #     {
        #         "orderId":1690615676032452985,
        #     }
        #
        # fetchOrders - spot
        #
        #     {
        #         "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
        #     }
        #
        # fetchOrders - future
        #
        #     {
        #         "orderId":1917641,
        #         "price":100,
        #         "origQty":10,
        #         "origAmount":10,
        #         "executedQty":1,
        #         "avgPrice":10000,
        #         "status":"INIT",
        #         "type":"LIMIT",
        #         "side":"BUY",
        #         "action":"OPEN",
        #         "transactTime":1686716571425
        #         "clientOrderId":4949299210
        #     }
        #
        status = self.parse_order_status(self.safe_string_2(order, 'status', 'orderStatus'))
        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') or (type == 'post_only')
        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,
            'triggerPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': None,
            'trades': fills,
        }, market)

    def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
        """
        create a market buy order by providing the symbol and cost
        :see: https://www.bitrue.com/api-docs#new-order-trade-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#new-order-trade-hmac-sha256
        :param str symbol: unified symbol of the market to create an order in
        :param float cost: how much you want to trade in units of the quote currency
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['swap']:
            raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports swap orders only')
        params['createMarketBuyOrderRequiresPrice'] = False
        return self.create_order(symbol, 'market', 'buy', cost, None, params)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#recent-trades-list
        :see: https://www.bitrue.com/api-docs#new-order-trade-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#new-order-trade-hmac-sha256
        :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 [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: *spot only* the price at which a trigger order is triggered at
        :param str [params.clientOrderId]: a unique id for the order, automatically generated if not sent
        :param decimal [params.leverage]: in future order, the leverage value of the order should consistent with the user contract configuration, default is 1
        :param str [params.timeInForce]: 'fok', 'ioc' or 'po'
        :param bool [params.postOnly]: default False
        :param bool [params.reduceOnly]: default False
         * EXCHANGE SPECIFIC PARAMETERS
        :param decimal [params.icebergQty]:
        :param long [params.recvWindow]:
        :param float [params.cost]: *swap market buy only* the quote quantity that can be used alternative for the amount
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        response = None
        data = None
        uppercaseType = type.upper()
        request: dict = {
            'side': side.upper(),
            'type': uppercaseType,
            # 'timeInForce': '',
            # '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),
        }
        if uppercaseType == 'LIMIT':
            if price is None:
                raise InvalidOrder(self.id + ' createOrder() requires a price argument')
            request['price'] = self.price_to_precision(symbol, price)
        if market['swap']:
            isMarket = uppercaseType == 'MARKET'
            timeInForce = self.safe_string_lower(params, 'timeInForce')
            postOnly = self.is_post_only(isMarket, None, params)
            if postOnly:
                request['type'] = 'POST_ONLY'
            elif timeInForce == 'fok':
                request['type'] = 'FOK'
            elif timeInForce == 'ioc':
                request['type'] = 'IOC'
            request['contractName'] = market['id']
            createMarketBuyOrderRequiresPrice = True
            createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
            if isMarket and (side == 'buy') and createMarketBuyOrderRequiresPrice:
                cost = self.safe_string(params, 'cost')
                params = self.omit(params, 'cost')
                if price is None and cost is None:
                    raise InvalidOrder(self.id + ' createOrder() requires the price argument with swap market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options["createMarketBuyOrderRequiresPrice"] = False to supply the cost in the amount argument(the exchange-specific behaviour)')
                else:
                    amountString = self.number_to_string(amount)
                    priceString = self.number_to_string(price)
                    quoteAmount = Precise.string_mul(amountString, priceString)
                    requestAmount = cost if (cost is not None) else quoteAmount
                    request['amount'] = self.cost_to_precision(symbol, requestAmount)
                    request['volume'] = self.cost_to_precision(symbol, requestAmount)
            else:
                request['amount'] = self.parse_to_numeric(amount)
                request['volume'] = self.parse_to_numeric(amount)
            request['positionType'] = 1
            reduceOnly = self.safe_value_2(params, 'reduceOnly', 'reduce_only')
            request['open'] = 'CLOSE' if reduceOnly else 'OPEN'
            leverage = self.safe_string(params, 'leverage', '1')
            request['leverage'] = self.parse_to_numeric(leverage)
            params = self.omit(params, ['leverage', 'reduceOnly', 'reduce_only', 'timeInForce'])
            if market['linear']:
                response = self.fapiV2PrivatePostOrder(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV2PrivatePostOrder(self.extend(request, params))
            data = self.safe_value(response, 'data', {})
        elif market['spot']:
            request['symbol'] = market['id']
            request['quantity'] = self.amount_to_precision(symbol, amount)
            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)
            clientOrderId = self.safe_string_2(params, 'newClientOrderId', 'clientOrderId')
            if clientOrderId is not None:
                params = self.omit(params, ['newClientOrderId', 'clientOrderId'])
                request['newClientOrderId'] = clientOrderId
            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 = self.spotV1PrivatePostOrder(self.extend(request, params))
            data = response
        else:
            raise NotSupported(self.id + ' createOrder only support spot & swap markets')
        #
        # spot
        #
        #     {
        #         "symbol": "BTCUSDT",
        #         "orderId": 307650651173648896,
        #         "orderIdStr": "307650651173648896",
        #         "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
        #         "transactTime": 1507725176595
        #     }
        #
        # swap
        #
        #     {
        #         "code": "0",
        #         "msg": "Success",
        #         "data": {
        #             "orderId": 1690615676032452985
        #         }
        #     }
        #
        return self.parse_order(data, market)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#query-order-user_data
        :see: https://www.bitrue.com/api-docs#query-order-user_data-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#query-order-user_data-hmac-sha256
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        origClientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
        params = self.omit(params, ['origClientOrderId', 'clientOrderId'])
        response = None
        data = None
        request: dict = {}
        if origClientOrderId is None:
            request['orderId'] = id
        else:
            if market['swap']:
                request['clientOrderId'] = origClientOrderId
            else:
                request['origClientOrderId'] = origClientOrderId
        if market['swap']:
            request['contractName'] = market['id']
            if market['linear']:
                response = self.fapiV2PrivateGetOrder(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV2PrivateGetOrder(self.extend(request, params))
            data = self.safe_value(response, 'data', {})
        elif market['spot']:
            request['orderId'] = id  # spot market id is mandatory
            request['symbol'] = market['id']
            response = self.spotV1PrivateGetOrder(self.extend(request, params))
            data = response
        else:
            raise NotSupported(self.id + ' fetchOrder only support spot & swap markets')
        #
        # spot
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "orderId": 1,
        #         "clientOrderId": "myOrder1",
        #         "price": "0.1",
        #         "origQty": "1.0",
        #         "executedQty": "0.0",
        #         "cummulativeQuoteQty": "0.0",
        #         "status": "NEW",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #         "stopPrice": "0.0",
        #         "icebergQty": "0.0",
        #         "time": 1499827319559,
        #         "updateTime": 1499827319559,
        #         "isWorking": True
        #     }
        #
        # swap
        #
        #     {
        #         "code":0,
        #         "msg":"success",
        #         "data":{
        #             "orderId":1917641,
        #             "price":100,
        #             "origQty":10,
        #             "origAmount":10,
        #             "executedQty":1,
        #             "avgPrice":10000,
        #             "status":"INIT",
        #             "type":"LIMIT",
        #             "side":"BUY",
        #             "action":"OPEN",
        #             "transactTime":1686716571425
        #             "clientOrderId":4949299210
        #         }
        #     }
        #
        return self.parse_order(data, market)

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#all-orders-user_data
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchClosedOrders only support spot markets')
        request: dict = {
            '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 = self.spotV1PrivateGetAllOrders(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)

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#current-open-orders-user_data
        :see: https://www.bitrue.com/api-docs#current-all-open-orders-user_data-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#current-all-open-orders-user_data-hmac-sha256
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of open order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        response = None
        data = None
        request: dict = {}
        if market['swap']:
            request['contractName'] = market['id']
            if market['linear']:
                response = self.fapiV2PrivateGetOpenOrders(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV2PrivateGetOpenOrders(self.extend(request, params))
            data = self.safe_value(response, 'data', [])
        elif market['spot']:
            request['symbol'] = market['id']
            response = self.spotV1PrivateGetOpenOrders(self.extend(request, params))
            data = response
        else:
            raise NotSupported(self.id + ' fetchOpenOrders only support spot & swap markets')
        #
        # spot
        #
        #     [
        #         {
        #             "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
        #         }
        #     ]
        #
        # swap
        #
        #      {
        #          "code": "0",
        #          "msg": "Success",
        #          "data": [{
        #                  "orderId": 1917641,
        #                  "clientOrderId": "2488514315",
        #                  "price": 100,
        #                  "origQty": 10,
        #                  "origAmount": 10,
        #                  "executedQty": 1,
        #                  "avgPrice": 12451,
        #                  "status": "INIT",
        #                  "type": "LIMIT",
        #                  "side": "BUY",
        #                  "action": "OPEN",
        #                  "transactTime": 1686717303975
        #              }
        #          ]
        #      }
        #
        return self.parse_orders(data, market, since, limit)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#cancel-order-trade
        :see: https://www.bitrue.com/api-docs#cancel-order-trade-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#cancel-order-trade-hmac-sha256
        :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 exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        origClientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
        params = self.omit(params, ['origClientOrderId', 'clientOrderId'])
        response = None
        data = None
        request: dict = {}
        if origClientOrderId is None:
            request['orderId'] = id
        else:
            if market['swap']:
                request['clientOrderId'] = origClientOrderId
            else:
                request['origClientOrderId'] = origClientOrderId
        if market['swap']:
            request['contractName'] = market['id']
            if market['linear']:
                response = self.fapiV2PrivatePostCancel(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV2PrivatePostCancel(self.extend(request, params))
            data = self.safe_value(response, 'data', {})
        elif market['spot']:
            request['symbol'] = market['id']
            response = self.spotV1PrivateDeleteOrder(self.extend(request, params))
            data = response
        else:
            raise NotSupported(self.id + ' cancelOrder only support spot & swap markets')
        #
        # spot
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "origClientOrderId": "myOrder1",
        #         "orderId": 1,
        #         "clientOrderId": "cancelMyOrder1"
        #     }
        #
        # swap
        #
        #     {
        #         "code": "0",
        #         "msg": "Success",
        #         "data": {
        #             "orderId": 1690615847831143159
        #         }
        #     }
        #
        return self.parse_order(data, market)

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders in a market
        :see: https://www.bitrue.com/api-docs#cancel-all-open-orders-trade-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#cancel-all-open-orders-trade-hmac-sha256
        :param str symbol: unified market symbol of the market to cancel orders in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
        :returns dict[]: a list of `order structures <https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        response = None
        data = None
        if market['swap']:
            request: dict = {
                'contractName': market['id'],
            }
            if market['linear']:
                response = self.fapiV2PrivatePostAllOpenOrders(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV2PrivatePostAllOpenOrders(self.extend(request, params))
            data = self.safe_value(response, 'data', [])
        else:
            raise NotSupported(self.id + ' cancelAllOrders only support future markets')
        #
        # swap
        #
        #      {
        #          'code': '0',
        #          'msg': 'Success',
        #          'data': null
        #      }
        #
        return self.parse_orders(data, market)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#account-trade-list-user_data
        :see: https://www.bitrue.com/api-docs#account-trade-list-user_data-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#account-trade-list-user_data-hmac-sha256
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        market = self.market(symbol)
        response = None
        data = None
        request: dict = {}
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            if limit > 1000:
                limit = 1000
            request['limit'] = limit
        if market['swap']:
            request['contractName'] = market['id']
            if market['linear']:
                response = self.fapiV2PrivateGetMyTrades(self.extend(request, params))
            elif market['inverse']:
                response = self.dapiV2PrivateGetMyTrades(self.extend(request, params))
            data = self.safe_value(response, 'data', [])
        elif market['spot']:
            request['symbol'] = market['id']
            response = self.spotV2PrivateGetMyTrades(self.extend(request, params))
            data = response
        else:
            raise NotSupported(self.id + ' fetchMyTrades only support spot & swap markets')
        #
        # spot
        #
        #     [
        #         {
        #             "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
        #         }
        #     ]
        #
        # swap
        #
        #     {
        #         "code":"0",
        #         "msg":"Success",
        #         "data":[
        #             {
        #                 "tradeId":12,
        #                 "price":0.9,
        #                 "qty":1,
        #                 "amount":9,
        #                 "contractName":"E-SAND-USDT",
        #                 "side":"BUY",
        #                 "fee":"0.0018",
        #                 "bidId":1558124009467904992,
        #                 "askId":1558124043827644908,
        #                 "bidUserId":10294,
        #                 "askUserId":10467,
        #                 "isBuyer":true,
        #                 "isMaker":true,
        #                 "ctime":1678426306000
        #             }
        #         ]
        #     }
        #
        return self.parse_trades(data, market, since, limit)

    def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#deposit-history--withdraw_data
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchDeposits() requires a code argument')
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            '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 = self.spotV1PrivateGetDepositHistory(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_list(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit)

    def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#withdraw-history--withdraw_data
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires a code argument')
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            '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 = self.spotV1PrivateGetWithdrawHistory(self.extend(request, params))
        #
        #    {
        #        "code": 200,
        #        "msg": "succ",
        #        "data": [
        #            {
        #                "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
        #            }
        #        ]
        #    }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_transactions(data, currency)

    def parse_transaction_status_by_type(self, status, type=None):
        statusesByType: dict = {
            '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: dict, currency: Currency = None) -> Transaction:
        #
        # 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",
        #         "withdrawId": 1156423,
        #         "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83"
        #     }
        #
        id = self.safe_string_2(transaction, 'id', 'withdrawId')
        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_2(transaction, 'symbol', 'coin')
        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,
            'comment': None,
            'fee': fee,
        }

    def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#withdraw-commit--withdraw_data
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'coin': currency['id'],
            'amount': amount,
            'addressTo': address,
            # 'chainName': chainName,  # 'ERC20', 'TRC20', 'SOL'
            # 'addressMark': '',  # mark of address
            # 'addrType': '',  # type of address
            # 'tag': tag,
        }
        networkCode = None
        networkCode, params = self.handle_network_code_and_params(params)
        if networkCode is not None:
            request['chainName'] = self.network_code_to_id(networkCode)
        if tag is not None:
            request['tag'] = tag
        response = self.spotV1PrivatePostWithdrawCommit(self.extend(request, params))
        #
        #     {
        #         "code": 200,
        #         "msg": "succ",
        #         "data": {
        #             "msg": null,
        #             "amount": 1000,
        #             "fee": 1,
        #             "ctime": null,
        #             "coin": "usdt_erc20",
        #             "withdrawId": 1156423,
        #             "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83"
        #         }
        #     }
        #
        data = self.safe_dict(response, 'data', {})
        return self.parse_transaction(data, currency)

    def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
        #
        #   {
        #       "coin": "adx",
        #       "coinFulName": "Ambire AdEx",
        #       "chains": ["BSC"],
        #       "chainDetail": [[Object]]
        #   }
        #
        chainDetails = self.safe_value(fee, 'chainDetail', [])
        chainDetailLength = len(chainDetails)
        result: dict = {
            'info': fee,
            'withdraw': {
                'fee': None,
                'percentage': None,
            },
            'deposit': {
                'fee': None,
                'percentage': None,
            },
            'networks': {},
        }
        if chainDetailLength != 0:
            for i in range(0, chainDetailLength):
                chainDetail = chainDetails[i]
                networkId = self.safe_string(chainDetail, 'chain')
                currencyCode = self.safe_string(currency, 'code')
                networkCode = self.network_id_to_code(networkId, currencyCode)
                result['networks'][networkCode] = {
                    'deposit': {'fee': None, 'percentage': None},
                    'withdraw': {'fee': self.safe_number(chainDetail, 'withdrawFee'), 'percentage': False},
                }
                if chainDetailLength == 1:
                    result['withdraw']['fee'] = self.safe_number(chainDetail, 'withdrawFee')
                    result['withdraw']['percentage'] = False
        return result

    def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
        """
        fetch deposit and withdraw fees
        :see: https://github.com/Bitrue-exchange/Spot-official-api-docs#exchangeInfo_endpoint
        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `fee structures <https://docs.ccxt_zagrava.com/#/?id=fee-structure>`
        """
        self.load_markets()
        response = self.spotV1PublicGetExchangeInfo(params)
        coins = self.safe_list(response, 'coins')
        return self.parse_deposit_withdraw_fees(coins, codes, 'coin')

    def parse_transfer(self, transfer, currency=None):
        #
        #     fetchTransfers
        #
        #     {
        #         'transferType': 'wallet_to_contract',
        #         'symbol': 'USDT',
        #         'amount': 1.0,
        #         'status': 1,
        #         'ctime': 1685404575000
        #     }
        #
        #     transfer
        #
        #     {}
        #
        transferType = self.safe_string(transfer, 'transferType')
        fromAccount = None
        toAccount = None
        if transferType is not None:
            accountSplit = transferType.split('_to_')
            fromAccount = self.safe_string(accountSplit, 0)
            toAccount = self.safe_string(accountSplit, 1)
        timestamp = self.safe_integer(transfer, 'ctime')
        return {
            'info': transfer,
            'id': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'currency': self.safe_string(currency, 'code'),
            'amount': self.safe_number(transfer, 'amount'),
            'fromAccount': fromAccount,
            'toAccount': toAccount,
            'status': 'ok',
        }

    def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> TransferEntries:
        """
        fetch a history of internal transfers made on an account
        :see: https://www.bitrue.com/api-docs#get-future-account-transfer-history-list-user_data-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#get-future-account-transfer-history-list-user_data-hmac-sha256
        :param str code: unified currency code of the currency transferred
        :param int [since]: the earliest time in ms to fetch transfers for
        :param int [limit]: the maximum number of transfers structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch transfers for
        :param str [params.type]: transfer type wallet_to_contract or contract_to_wallet
        :returns dict[]: a list of `transfer structures <https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#transfer-structure>`
        """
        self.load_markets()
        type = self.safe_string_2(params, 'type', 'transferType')
        request: dict = {
            'transferType': type,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['coinSymbol'] = currency['id']
        if since is not None:
            request['beginTime'] = since
        if limit is not None:
            if limit > 200:
                limit = 200
            request['limit'] = limit
        until = self.safe_integer(params, 'until')
        if until is not None:
            params = self.omit(params, 'until')
            request['endTime'] = until
        response = self.fapiV2PrivateGetFuturesTransferHistory(self.extend(request, params))
        #
        #     {
        #         'code': '0',
        #         'msg': 'Success',
        #         'data': [{
        #             'transferType': 'wallet_to_contract',
        #             'symbol': 'USDT',
        #             'amount': 1.0,
        #             'status': 1,
        #             'ctime': 1685404575000
        #         }]
        #     }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_transfers(data, currency, since, limit)

    def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account
        :see: https://www.bitrue.com/api-docs#new-future-account-transfer-user_data-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#user-commission-rate-user_data-hmac-sha256
        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        accountTypes = self.safe_value(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountTypes, fromAccount, fromAccount)
        toId = self.safe_string(accountTypes, toAccount, toAccount)
        request: dict = {
            'coinSymbol': currency['id'],
            'amount': self.currency_to_precision(code, amount),
            'transferType': fromId + '_to_' + toId,
        }
        response = self.fapiV2PrivatePostFuturesTransfer(self.extend(request, params))
        #
        #     {
        #         'code': '0',
        #         'msg': 'Success',
        #         'data': null
        #     }
        #
        data = self.safe_dict(response, 'data', {})
        return self.parse_transfer(data, currency)

    def set_leverage(self, leverage: Int, symbol: Str = None, params={}):
        """
        set the level of leverage for a market
        :see: https://www.bitrue.com/api-docs#change-initial-leverage-trade-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#change-initial-leverage-trade-hmac-sha256
        :param float leverage: the rate of leverage
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from the exchange
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
        if (leverage < 1) or (leverage > 125):
            raise BadRequest(self.id + ' leverage should be between 1 and 125')
        self.load_markets()
        market = self.market(symbol)
        response = None
        request: dict = {
            'contractName': market['id'],
            'leverage': leverage,
        }
        if not market['swap']:
            raise NotSupported(self.id + ' setLeverage only support swap markets')
        if market['linear']:
            response = self.fapiV2PrivatePostLevelEdit(self.extend(request, params))
        elif market['inverse']:
            response = self.dapiV2PrivatePostLevelEdit(self.extend(request, params))
        return response

    def parse_margin_modification(self, data, market=None) -> MarginModification:
        #
        # setMargin
        #
        #     {
        #         "code": 0,
        #         "msg": "success"
        #         "data": null
        #     }
        #
        return {
            'info': data,
            'symbol': market['symbol'],
            'type': None,
            'marginMode': 'isolated',
            'amount': None,
            'total': None,
            'code': None,
            'status': None,
            'timestamp': None,
            'datetime': None,
        }

    def set_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """
        Either adds or reduces margin in an isolated position in order to set the margin to a specific value
        :see: https://www.bitrue.com/api-docs#modify-isolated-position-margin-trade-hmac-sha256
        :see: https://www.bitrue.com/api_docs_includes_file/delivery.html#modify-isolated-position-margin-trade-hmac-sha256
        :param str symbol: unified market symbol of the market to set margin in
        :param float amount: the amount to set the margin to
        :param dict [params]: parameters specific to the exchange API endpoint
        :returns dict: A `margin structure <https://github.com/ccxt_zagrava/ccxt_zagrava/wiki/Manual#add-margin-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['swap']:
            raise NotSupported(self.id + ' setMargin only support swap markets')
        response = None
        request: dict = {
            'contractName': market['id'],
            'amount': self.parse_to_numeric(amount),
        }
        if market['linear']:
            response = self.fapiV2PrivatePostPositionMargin(self.extend(request, params))
        elif market['inverse']:
            response = self.dapiV2PrivatePostPositionMargin(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "success"
        #         "data": null
        #     }
        #
        return self.parse_margin_modification(response, market)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        type = self.safe_string(api, 0)
        version = self.safe_string(api, 1)
        access = self.safe_string(api, 2)
        url = None
        if type == 'api' and version == 'kline':
            url = self.urls['api'][type]
        else:
            url = self.urls['api'][type] + '/' + version
        url = url + '/' + 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)
            if type == 'spot':
                query = self.urlencode(self.extend({
                    'timestamp': self.nonce(),
                    'recvWindow': recvWindow,
                }, params))
                signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256)
                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:
                timestamp = str(self.nonce())
                signPath = None
                if type == 'fapi':
                    signPath = '/fapi'
                elif type == 'dapi':
                    signPath = '/dapi'
                signPath = signPath + '/' + version + '/' + path
                signMessage = timestamp + method + signPath
                if method == 'GET':
                    keys = list(params.keys())
                    keysLength = len(keys)
                    if keysLength > 0:
                        signMessage += '?' + self.urlencode(params)
                    signature = self.hmac(self.encode(signMessage), self.encode(self.secret), hashlib.sha256)
                    headers = {
                        'X-CH-APIKEY': self.apiKey,
                        'X-CH-SIGN': signature,
                        'X-CH-TS': timestamp,
                    }
                    url += '?' + self.urlencode(params)
                else:
                    query = self.extend({
                        'recvWindow': recvWindow,
                    }, params)
                    body = self.json(query)
                    signMessage += body
                    signature = self.hmac(self.encode(signMessage), self.encode(self.secret), hashlib.sha256)
                    headers = {
                        'Content-Type': 'application/json',
                        'X-CH-APIKEY': self.apiKey,
                        'X-CH-SIGN': signature,
                        'X-CH-TS': timestamp,
                    }
        else:
            if params:
                url += '?' + self.urlencode(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, 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 None  # 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_bool(response, 'success', True)
        if not success:
            messageInner = self.safe_string(response, 'msg')
            parsedMessage = None
            if messageInner is not None:
                try:
                    parsedMessage = json.loads(messageInner)
                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_zagrava/ccxt_zagrava/issues/6501
            # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/7742
            if (error == '200') or Precise.string_equals(error, '0'):
                return None
            # 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)
        return None

    def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
        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)
