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

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

from ccxt.base.exchange import Exchange
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class bitmart(Exchange):

    def describe(self):
        return self.deep_extend(super(bitmart, self).describe(), {
            'id': 'bitmart',
            'name': 'BitMart',
            'countries': ['US', 'CN', 'HK', 'KR'],
            # 150 per 5 seconds = 30 per second
            # rateLimit = 1000ms / 30 ~= 33.334
            'rateLimit': 33.34,
            'version': 'v2',
            'certified': True,
            'pro': True,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': True,
                'swap': None,  # has but unimplemented
                'future': False,
                'option': None,
                'borrowMargin': True,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': False,
                'createOrder': True,
                'createPostOnlyOrder': True,
                'createStopLimitOrder': False,
                'createStopMarketOrder': False,
                'createStopOrder': False,
                'fetchBalance': True,
                'fetchBorrowInterest': True,
                'fetchBorrowRate': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchBorrowRates': True,
                'fetchCanceledOrders': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDeposit': True,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': True,
                'fetchDepositWithdrawFee': True,
                'fetchDepositWithdrawFees': False,
                'fetchFundingHistory': None,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchOrderTrades': True,
                'fetchPositionMode': False,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': False,
                'fetchTransactionFee': True,
                'fetchTransactionFees': False,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawAddressesByNetwork': False,
                'fetchWithdrawal': True,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'repayMargin': True,
                'setLeverage': False,
                'setMarginMode': False,
                'transfer': True,
                'withdraw': True,
            },
            'hostname': 'bitmart.com',  # bitmart.info, bitmart.news for Hong Kong users
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/129991357-8f47464b-d0f4-41d6-8a82-34122f0d1398.jpg',
                'api': {
                    'rest': 'https://api-cloud.{hostname}',  # bitmart.info for Hong Kong users
                },
                'www': 'https://www.bitmart.com/',
                'doc': 'https://developer-pro.bitmart.com/',
                'referral': {
                    'url': 'http://www.bitmart.com/?r=rQCFLh',
                    'discount': 0.3,
                },
                'fees': 'https://www.bitmart.com/fee/en',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'uid': True,
            },
            'api': {
                'public': {
                    'get': {
                        'system/time': 3,
                        'system/service': 3,
                        # spot markets
                        'spot/v1/currencies': 7.5,
                        'spot/v1/symbols': 7.5,
                        'spot/v1/symbols/details': 5,
                        'spot/v1/ticker': 5,
                        'spot/v2/ticker': 5,
                        'spot/v1/steps': 30,
                        'spot/v1/symbols/kline': 5,
                        'spot/v1/symbols/book': 5,
                        'spot/v1/symbols/trades': 5,
                        # contract markets
                        'contract/v1/tickers': 15,
                        'contract/public/details': 5,
                        'contract/public/depth': 5,
                        'contract/public/open-interest': 30,
                        'contract/public/funding-rate': 30,
                        'contract/public/kline': 5,
                    },
                },
                'private': {
                    'get': {
                        # sub-account
                        'account/sub-account/v1/transfer-list': 7.5,
                        'account/sub-account/v1/transfer-history': 7.5,
                        'account/sub-account/main/v1/wallet': 5,
                        'account/sub-account/main/v1/subaccount-list': 7.5,
                        # account
                        'account/v1/wallet': 5,
                        'account/v1/currencies': 30,
                        'spot/v1/wallet': 5,
                        'account/v1/deposit/address': 30,
                        'account/v1/withdraw/charge': 32,  # should be 30 but errors
                        'account/v2/deposit-withdraw/history': 7.5,
                        'account/v1/deposit-withdraw/detail': 7.5,
                        # order
                        'spot/v1/order_detail': 1,
                        'spot/v2/orders': 5,
                        'spot/v1/trades': 5,
                        # newer order endpoint
                        'spot/v2/trades': 5,
                        'spot/v3/orders': 5,
                        'spot/v2/order_detail': 1,
                        # margin
                        'spot/v1/margin/isolated/borrow_record': 1,
                        'spot/v1/margin/isolated/repay_record': 1,
                        'spot/v1/margin/isolated/pairs': 1,
                        'spot/v1/margin/isolated/account': 6,
                        'spot/v1/trade_fee': 6,
                        'spot/v1/user_fee': 6,
                        # contract
                        'contract/private/assets-detail': 5,
                        'contract/private/order': 2,
                        'contract/private/order-history': 10,
                        'contract/private/position': 10,
                    },
                    'post': {
                        # sub-account endpoints
                        'account/sub-account/main/v1/sub-to-main': 30,
                        'account/sub-account/sub/v1/sub-to-main': 30,
                        'account/sub-account/main/v1/main-to-sub': 30,
                        'account/sub-account/sub/v1/sub-to-sub': 30,
                        'account/sub-account/main/v1/sub-to-sub': 30,
                        # account
                        'account/v1/withdraw/apply': 7.5,
                        # transaction and trading
                        'spot/v1/submit_order': 1,
                        'spot/v1/batch_orders': 1,
                        'spot/v2/cancel_order': 1,
                        'spot/v1/cancel_orders': 15,
                        # newer endpoint
                        'spot/v3/cancel_order': 1,
                        'spot/v2/batch_orders': 1,
                        'spot/v2/submit_order': 1,
                        # margin
                        'spot/v1/margin/submit_order': 1,
                        'spot/v1/margin/isolated/borrow': 6,
                        'spot/v1/margin/isolated/repay': 6,
                        'spot/v1/margin/isolated/transfer': 6,
                        # contract
                        'contract/private/trades': 10,
                    },
                },
            },
            'timeframes': {
                '1m': 1,
                '3m': 3,
                '5m': 5,
                '15m': 15,
                '30m': 30,
                '45m': 45,
                '1h': 60,
                '2h': 120,
                '3h': 180,
                '4h': 240,
                '1d': 1440,
                '1w': 10080,
                '1M': 43200,
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': self.parse_number('0.0025'),
                    'maker': self.parse_number('0.0025'),
                    'tiers': {
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.0020')],
                            [self.parse_number('10'), self.parse_number('0.18')],
                            [self.parse_number('50'), self.parse_number('0.0016')],
                            [self.parse_number('250'), self.parse_number('0.0014')],
                            [self.parse_number('1000'), self.parse_number('0.0012')],
                            [self.parse_number('5000'), self.parse_number('0.0010')],
                            [self.parse_number('25000'), self.parse_number('0.0008')],
                            [self.parse_number('50000'), self.parse_number('0.0006')],
                        ],
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.001')],
                            [self.parse_number('10'), self.parse_number('0.0009')],
                            [self.parse_number('50'), self.parse_number('0.0008')],
                            [self.parse_number('250'), self.parse_number('0.0007')],
                            [self.parse_number('1000'), self.parse_number('0.0006')],
                            [self.parse_number('5000'), self.parse_number('0.0005')],
                            [self.parse_number('25000'), self.parse_number('0.0004')],
                            [self.parse_number('50000'), self.parse_number('0.0003')],
                        ],
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    # general errors
                    '30000': ExchangeError,  # 404, Not found
                    '30001': AuthenticationError,  # 401, Header X-BM-KEY is empty
                    '30002': AuthenticationError,  # 401, Header X-BM-KEY not found
                    '30003': AccountSuspended,  # 401, Header X-BM-KEY has frozen
                    '30004': AuthenticationError,  # 401, Header X-BM-SIGN is empty
                    '30005': AuthenticationError,  # 401, Header X-BM-SIGN is wrong
                    '30006': AuthenticationError,  # 401, Header X-BM-TIMESTAMP is empty
                    '30007': AuthenticationError,  # 401, Header X-BM-TIMESTAMP range. Within a minute
                    '30008': AuthenticationError,  # 401, Header X-BM-TIMESTAMP invalid format
                    '30010': PermissionDenied,  # 403, IP is forbidden. We recommend enabling IP whitelist for API trading. After that reauth your account
                    '30011': AuthenticationError,  # 403, Header X-BM-KEY over expire time
                    '30012': AuthenticationError,  # 403, Header X-BM-KEY is forbidden to request it
                    '30013': RateLimitExceeded,  # 429, Request too many requests
                    '30014': ExchangeNotAvailable,  # 503, Service unavailable
                    # funding account errors
                    '60000': BadRequest,  # 400, Invalid request(maybe the body is empty, or the int parameter passes string data)
                    '60001': BadRequest,  # 400, Asset account type does not exist
                    '60002': BadRequest,  # 400, currency does not exist
                    '60003': ExchangeError,  # 400, Currency has been closed recharge channel, if there is any problem, please consult customer service
                    '60004': ExchangeError,  # 400, Currency has been closed withdraw channel, if there is any problem, please consult customer service
                    '60005': ExchangeError,  # 400, Minimum amount is %s
                    '60006': ExchangeError,  # 400, Maximum withdraw precision is %d
                    '60007': InvalidAddress,  # 400, Only withdrawals from added addresses are allowed
                    '60008': InsufficientFunds,  # 400, Balance not enough
                    '60009': ExchangeError,  # 400, Beyond the limit
                    '60010': ExchangeError,  # 400, Withdraw id or deposit id not found
                    '60011': InvalidAddress,  # 400, Address is not valid
                    '60012': ExchangeError,  # 400, This action is not supported in self currency(If IOTA, HLX recharge and withdraw calls are prohibited)
                    '60020': PermissionDenied,  # 403, Your account is not allowed to recharge
                    '60021': PermissionDenied,  # 403, Your account is not allowed to withdraw
                    '60022': PermissionDenied,  # 403, No withdrawals for 24 hours
                    '60030': BadRequest,  # 405, Method Not Allowed
                    '60031': BadRequest,  # 415, Unsupported Media Type
                    '60050': ExchangeError,  # 500, User account not found
                    '60051': ExchangeError,  # 500, Internal Server Error
                    '61001': InsufficientFunds,  # {"message":"Balance not enough","code":61001,"trace":"b85ea1f8-b9af-4001-ac5f-9e061fe93d78","data":{}}
                    '61003': BadRequest,  # {"message":"sub-account not found","code":61003,"trace":"b35ec2fd-0bc9-4ef2-a3c0-6f78d4f335a4","data":{}}
                    # spot errors
                    '50000': BadRequest,  # 400, Bad Request
                    '50001': BadSymbol,  # 400, Symbol not found
                    '50002': BadRequest,  # 400, From Or To format error
                    '50003': BadRequest,  # 400, Step format error
                    '50004': BadRequest,  # 400, Kline size over 500
                    '50005': OrderNotFound,  # 400, Order Id not found
                    '50006': InvalidOrder,  # 400, Minimum size is %s
                    '50007': InvalidOrder,  # 400, Maximum size is %s
                    '50008': InvalidOrder,  # 400, Minimum price is %s
                    '50009': InvalidOrder,  # 400, Minimum count*price is %s
                    '50010': InvalidOrder,  # 400, RequestParam size is required
                    '50011': InvalidOrder,  # 400, RequestParam price is required
                    '50012': InvalidOrder,  # 400, RequestParam notional is required
                    '50013': InvalidOrder,  # 400, Maximum limit*offset is %d
                    '50014': BadRequest,  # 400, RequestParam limit is required
                    '50015': BadRequest,  # 400, Minimum limit is 1
                    '50016': BadRequest,  # 400, Maximum limit is %d
                    '50017': BadRequest,  # 400, RequestParam offset is required
                    '50018': BadRequest,  # 400, Minimum offset is 1
                    '50019': BadRequest,  # 400, Maximum price is %s
                    '51004': InsufficientFunds,  # {"message":"Exceed the maximum number of borrows available.","code":51004,"trace":"4030b753-9beb-44e6-8352-1633c5edcd47","data":{}}
                    # '50019': ExchangeError,  # 400, Invalid status. validate status is [1=Failed, 2=Success, 3=Frozen Failed, 4=Frozen Success, 5=Partially Filled, 6=Fully Fulled, 7=Canceling, 8=Canceled
                    '50020': InsufficientFunds,  # 400, Balance not enough
                    '50021': BadRequest,  # 400, Invalid %s
                    '50022': ExchangeNotAvailable,  # 400, Service unavailable
                    '50023': BadSymbol,  # 400, This Symbol can't place order by api
                    '50029': InvalidOrder,  # {"message":"param not match : size * price >=1000","code":50029,"trace":"f931f030-b692-401b-a0c5-65edbeadc598","data":{}}
                    '50030': InvalidOrder,  # {"message":"Order is already canceled","code":50030,"trace":"8d6f64ee-ad26-45a4-9efd-1080f9fca1fa","data":{}}
                    '50032': OrderNotFound,  # {"message":"Order does not exist","code":50032,"trace":"8d6b482d-4bf2-4e6c-aab2-9dcd22bf2481","data":{}}
                    # below Error codes used interchangeably for both failed postOnly and IOC orders depending on market price and order side
                    '50035': InvalidOrder,  # {"message":"The price is low and there is no matching depth","code":50035,"trace":"677f01c7-8b88-4346-b097-b4226c75c90e","data":{}}
                    '50034': InvalidOrder,  # {"message":"The price is high and there is no matching depth","code":50034,"trace":"ebfae59a-ba69-4735-86b2-0ed7b9ca14ea","data":{}}
                    '51011': InvalidOrder,  # {"message":"param not match : size * price >=5","code":51011,"trace":"525e1d27bfd34d60b2d90ba13a7c0aa9.74.16696421352220797","data":{}}
                    '53000': AccountSuspended,  # 403, Your account is frozen due to security policies. Please contact customer service
                    '53001': AccountSuspended,  # {"message":"Your kyc country is restricted. Please contact customer service.","code":53001,"trace":"8b445940-c123-4de9-86d7-73c5be2e7a24","data":{}}
                    '57001': BadRequest,  # 405, Method Not Allowed
                    '58001': BadRequest,  # 415, Unsupported Media Type
                    '59001': ExchangeError,  # 500, User account not found
                    '59002': ExchangeError,  # 500, Internal Server Error
                    # contract errors
                    '40001': ExchangeError,  # 400, Cloud account not found
                    '40002': ExchangeError,  # 400, out_trade_no not found
                    '40003': ExchangeError,  # 400, out_trade_no already existed
                    '40004': ExchangeError,  # 400, Cloud account count limit
                    '40005': ExchangeError,  # 400, Transfer vol precision error
                    '40006': PermissionDenied,  # 400, Invalid ip error
                    '40007': BadRequest,  # 400, Parse parameter error
                    '40008': InvalidNonce,  # 400, Check nonce error
                    '40009': BadRequest,  # 400, Check ver error
                    '40010': BadRequest,  # 400, Not found func error
                    '40011': BadRequest,  # 400, Invalid request
                    '40012': ExchangeError,  # 500, System error
                    '40013': ExchangeError,  # 400, Access too often" CLIENT_TIME_INVALID, "Please check your system time.
                    '40014': BadSymbol,  # 400, This contract is offline
                    '40015': BadSymbol,  # 400, This contract's exchange has been paused
                    '40016': InvalidOrder,  # 400, This order would trigger user position liquidate
                    '40017': InvalidOrder,  # 400, It is not possible to open and close simultaneously in the same position
                    '40018': InvalidOrder,  # 400, Your position is closed
                    '40019': ExchangeError,  # 400, Your position is in liquidation delegating
                    '40020': InvalidOrder,  # 400, Your position volume is not enough
                    '40021': ExchangeError,  # 400, The position is not exsit
                    '40022': ExchangeError,  # 400, The position is not isolated
                    '40023': ExchangeError,  # 400, The position would liquidate when sub margin
                    '40024': ExchangeError,  # 400, The position would be warnning of liquidation when sub margin
                    '40025': ExchangeError,  # 400, The position’s margin shouldn’t be lower than the base limit
                    '40026': ExchangeError,  # 400, You cross margin position is in liquidation delegating
                    '40027': InsufficientFunds,  # 400, You contract account available balance not enough
                    '40028': PermissionDenied,  # 400, Your plan order's count is more than system maximum limit.
                    '40029': InvalidOrder,  # 400, The order's leverage is too large.
                    '40030': InvalidOrder,  # 400, The order's leverage is too small.
                    '40031': InvalidOrder,  # 400, The deviation between current price and trigger price is too large.
                    '40032': InvalidOrder,  # 400, The plan order's life cycle is too long.
                    '40033': InvalidOrder,  # 400, The plan order's life cycle is too short.
                    '40034': BadSymbol,  # 400, This contract is not found
                    '53002': PermissionDenied,  # 403, Your account has not yet completed the kyc advanced certification, please complete first
                },
                'broad': {},
            },
            'commonCurrencies': {
                '$GM': 'GOLDMINER',
                '$HERO': 'Step Hero',
                '$PAC': 'PAC',
                'BP': 'BEYOND',
                'GDT': 'Gorilla Diamond',
                'GLD': 'Goldario',
                'MVP': 'MVP Coin',
                'TRU': 'Truebit',  # conflict with TrueFi
            },
            'options': {
                'networks': {
                    'TRX': 'TRC20',
                    'ETH': 'ERC20',
                },
                'defaultNetworks': {
                    'USDT': 'ERC20',
                },
                'defaultType': 'spot',  # 'spot', 'swap'
                'fetchBalance': {
                    'type': 'spot',  # 'spot', 'swap', 'account'
                },
                'accountsByType': {
                    'spot': 'spot',
                },
                'createMarketBuyOrderRequiresPrice': True,
            },
        })

    def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = self.publicGetSystemTime(params)
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"c4e5e5b7-fe9f-4191-89f7-53f6c5bf9030",
        #         "data":{
        #             "server_time":1599843709578
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.safe_integer(data, 'server_time')

    def fetch_status(self, params={}):
        """
        the latest known information on the availability of the exchange API
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `status structure <https://docs.ccxt.com/en/latest/manual.html#exchange-status-structure>`
        """
        options = self.safe_value(self.options, 'fetchStatus', {})
        defaultType = self.safe_string(self.options, 'defaultType')
        type = self.safe_string(options, 'type', defaultType)
        type = self.safe_string(params, 'type', type)
        params = self.omit(params, 'type')
        response = self.publicGetSystemService(params)
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "1d3f28b0-763e-4f78-90c4-5e3ad19dc595",
        #         "data": {
        #           "service": [
        #             {
        #               "title": "Spot API Stop",
        #               "service_type": "spot",
        #               "status": 2,
        #               "start_time": 1648639069125,
        #               "end_time": 1648639069125
        #             },
        #             {
        #               "title": "Contract API Stop",
        #               "service_type": "contract",
        #               "status": 2,
        #               "start_time": 1648639069125,
        #               "end_time": 1648639069125
        #             }
        #           ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        services = self.safe_value(data, 'service', [])
        servicesByType = self.index_by(services, 'service_type')
        if type == 'swap':
            type = 'contract'
        service = self.safe_value(servicesByType, type)
        status = None
        eta = None
        if service is not None:
            statusCode = self.safe_integer(service, 'status')
            if statusCode == 2:
                status = 'ok'
            else:
                status = 'maintenance'
                eta = self.safe_integer(service, 'end_time')
        return {
            'status': status,
            'updated': None,
            'eta': eta,
            'url': None,
            'info': response,
        }

    def fetch_spot_markets(self, params={}):
        response = self.publicGetSpotV1SymbolsDetails(params)
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"a67c9146-086d-4d3f-9897-5636a9bb26e1",
        #         "data":{
        #             "symbols":[
        #               {
        #                  "symbol": "BTC_USDT",
        #                  "symbol_id": 53,
        #                  "base_currency": "BTC",
        #                  "quote_currency": "USDT",
        #                  "base_min_size": "0.000010000000000000000000000000",
        #                  "base_max_size": "100000000.000000000000000000000000000000",
        #                  "price_min_precision": -1,
        #                  "price_max_precision": 2,
        #                  "quote_increment": "0.00001",  # Api docs says "The minimum order quantity is also the minimum order quantity increment", however I think they mistakenly use the term 'order quantity'
        #                  "expiration": "NA",
        #                  "min_buy_amount": "5.000000000000000000000000000000",
        #                  "min_sell_amount": "5.000000000000000000000000000000",
        #                  "trade_status": "trading"
        #               },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        symbols = self.safe_value(data, 'symbols', [])
        result = []
        for i in range(0, len(symbols)):
            market = symbols[i]
            id = self.safe_string(market, 'symbol')
            numericId = self.safe_integer(market, 'symbol_id')
            baseId = self.safe_string(market, 'base_currency')
            quoteId = self.safe_string(market, 'quote_currency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            minBuyCost = self.safe_string(market, 'min_buy_amount')
            minSellCost = self.safe_string(market, 'min_sell_amount')
            minCost = Precise.string_max(minBuyCost, minSellCost)
            baseMinSize = self.safe_number(market, 'base_min_size')
            result.append({
                'id': id,
                'numericId': numericId,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'active': True,
                'contract': False,
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': baseMinSize,
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'price_max_precision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': baseMinSize,
                        'max': self.safe_number(market, 'base_max_size'),
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.parse_number(minCost),
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    def fetch_contract_markets(self, params={}):
        response = self.publicGetContractV1Tickers(params)
        #
        #    {
        #        "message": "OK",
        #        "code": 1000,
        #        "trace": "045d13a8-4bc7-4974-9748-97d0ea183ef0",
        #        "data": {
        #            "tickers": [
        #                {
        #                    "contract_symbol": "RAYUSDT",
        #                    "last_price": "3.893",
        #                    "index_price": "3.90248043",
        #                    "last_funding_rate": "-0.00054285",
        #                    "price_change_percent_24h": "-6.955",
        #                    "volume_24h": "10450969.34602996",
        #                    "url": "https://futures.bitmart.com/en?symbol=RAYUSDT",
        #                    "high_price": "4.299",
        #                    "low_price": "3.887",
        #                    "legal_coin_price": "3.893056"
        #                },
        #                ...
        #            ]
        #        }
        #    }
        #
        data = self.safe_value(response, 'data', {})
        tickers = self.safe_value(data, 'tickers', [])
        result = []
        for i in range(0, len(tickers)):
            market = tickers[i]
            id = self.safe_string(market, 'contract_symbol')
            baseId = id[0:-4]
            quoteId = id[-4:]
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            settle = 'USDT'
            symbol = base + '/' + quote + ':' + settle
            result.append({
                'id': id,
                'numericId': None,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': settle,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'swap',
                'spot': False,
                'margin': False,
                'swap': True,
                'future': False,
                'option': False,
                'active': True,
                'contract': True,
                'linear': True,
                'inverse': False,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': None,
                    'price': None,
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for bitmart
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        spot = self.fetch_spot_markets(params)
        contract = self.fetch_contract_markets(params)
        return self.array_concat(spot, contract)

    def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.publicGetSpotV1Currencies(params)
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"8c768b3c-025f-413f-bec5-6d6411d46883",
        #         "data":{
        #             "currencies":[
        #                 {"currency":"MATIC","name":"Matic Network","withdraw_enabled":true,"deposit_enabled":true},
        #                 {"currency":"KTN","name":"Kasoutuuka News","withdraw_enabled":true,"deposit_enabled":false},
        #                 {"currency":"BRT","name":"Berith","withdraw_enabled":true,"deposit_enabled":true},
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        currencies = self.safe_value(data, 'currencies', [])
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'id')
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'name')
            withdrawEnabled = self.safe_value(currency, 'withdraw_enabled')
            depositEnabled = self.safe_value(currency, 'deposit_enabled')
            active = withdrawEnabled and depositEnabled
            result[code] = {
                'id': id,
                'code': code,
                'name': name,
                'info': currency,  # the original payload
                'active': active,
                'deposit': depositEnabled,
                'withdraw': withdrawEnabled,
                'fee': None,
                'precision': None,
                'limits': {
                    'amount': {'min': None, 'max': None},
                    'withdraw': {'min': None, 'max': None},
                },
            }
        return result

    def fetch_transaction_fee(self, code, params={}):
        """
        *DEPRECATED* please use fetchDepositWithdrawFee instead
        :param str code: unified currency code
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `fee structure <https://docs.ccxt.com/en/latest/manual.html#fee-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.privateGetAccountV1WithdrawCharge(self.extend(request, params))
        #
        #     {
        #         message: 'OK',
        #         code: '1000',
        #         trace: '3ecc0adf-91bd-4de7-aca1-886c1122f54f',
        #         data: {
        #             today_available_withdraw_BTC: '100.0000',
        #             min_withdraw: '0.005',
        #             withdraw_precision: '8',
        #             withdraw_fee: '0.000500000000000000000000000000'
        #         }
        #     }
        #
        data = response['data']
        withdrawFees = {}
        withdrawFees[code] = self.safe_number(data, 'withdraw_fee')
        return {
            'info': response,
            'withdraw': withdrawFees,
            'deposit': {},
        }

    def parse_deposit_withdraw_fee(self, fee, currency=None):
        #
        #    {
        #        today_available_withdraw_BTC: '100.0000',
        #        min_withdraw: '0.005',
        #        withdraw_precision: '8',
        #        withdraw_fee: '0.000500000000000000000000000000'
        #    }
        #
        return {
            'info': fee,
            'withdraw': {
                'fee': self.safe_number(fee, 'withdraw_fee'),
                'percentage': None,
            },
            'deposit': {
                'fee': None,
                'percentage': None,
            },
            'networks': {},
        }

    def fetch_deposit_withdraw_fee(self, code, params={}):
        """
        fetch the fee for deposits and withdrawals
        :param str code: unified currency code
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `fee structure <https://docs.ccxt.com/en/latest/manual.html#fee-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.privateGetAccountV1WithdrawCharge(self.extend(request, params))
        #
        #     {
        #         message: 'OK',
        #         code: '1000',
        #         trace: '3ecc0adf-91bd-4de7-aca1-886c1122f54f',
        #         data: {
        #             today_available_withdraw_BTC: '100.0000',
        #             min_withdraw: '0.005',
        #             withdraw_precision: '8',
        #             withdraw_fee: '0.000500000000000000000000000000'
        #         }
        #     }
        #
        data = response['data']
        return self.parse_deposit_withdraw_fee(data)

    def parse_ticker(self, ticker, market=None):
        #
        # spot
        #
        #      {
        #          "symbol": "SOLAR_USDT",
        #          "last_price": "0.020342",
        #          "quote_volume_24h": "56817.811802",
        #          "base_volume_24h": "2172060",
        #          "high_24h": "0.256000",
        #          "low_24h": "0.016980",
        #          "open_24h": "0.022309",
        #          "close_24h": "0.020342",
        #          "best_ask": "0.020389",
        #          "best_ask_size": "339.000000000000000000000000000000",
        #          "best_bid": "0.020342",
        #          "best_bid_size": "3369.000000000000000000000000000000",
        #          "fluctuation": "-0.0882",
        #          "url": "https://www.bitmart.com/trade?symbol=SOLAR_USDT",
        #          "timestamp": 1667403439367
        #      }
        #
        # swap
        #
        #      {
        #          "contract_symbol":"DOGEUSDT",
        #          "last_price":"0.130340",
        #          "index_price":"0.13048245",
        #          "last_funding_rate":"0.00002287",
        #          "price_change_percent_24h":"-2.074",
        #          "volume_24h":"113705028.59482228",
        #          "url":"https://futures.bitmart.com/en?symbol=DOGEUSDT",
        #          "high_price":"0.134520",
        #          "low_price":"0.128570",
        #          "legal_coin_price":"0.1302699"
        #      }
        #
        timestamp = self.safe_integer(ticker, 'timestamp', self.milliseconds())
        marketId = self.safe_string_2(ticker, 'symbol', 'contract_symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        last = self.safe_string_2(ticker, 'close_24h', 'last_price')
        percentage = self.safe_string(ticker, 'price_change_percent_24h')
        if percentage is None:
            percentageRaw = self.safe_string(ticker, 'fluctuation')
            if (percentageRaw is not None) and (percentageRaw != '0'):  # a few tickers show strictly '0' in fluctuation field
                direction = percentageRaw[0]
                percentage = direction + Precise.string_mul(percentageRaw.replace(direction, ''), '100')
            elif percentageRaw == '0':
                percentage = '0'
        baseVolume = self.safe_string(ticker, 'base_volume_24h')
        quoteVolume = self.safe_string(ticker, 'quote_volume_24h')
        quoteVolume = self.safe_string(ticker, 'volume_24h', quoteVolume)
        average = self.safe_string_2(ticker, 'avg_price', 'index_price')
        high = self.safe_string_2(ticker, 'high_24h', 'high_price')
        low = self.safe_string_2(ticker, 'low_24h', 'low_price')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': high,
            'low': low,
            'bid': self.safe_string(ticker, 'best_bid'),
            'bidVolume': self.safe_string(ticker, 'best_bid_size'),
            'ask': self.safe_string(ticker, 'best_ask'),
            'askVolume': self.safe_string(ticker, 'best_ask_size'),
            'vwap': None,
            'open': self.safe_string(ticker, 'open_24h'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {}
        method = None
        if market['swap']:
            method = 'publicGetContractV1Tickers'
            request['contract_symbol'] = market['id']
        elif market['spot']:
            method = 'publicGetSpotV1Ticker'
            request['symbol'] = market['id']
        response = getattr(self, method)(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"6aa5b923-2f57-46e3-876d-feca190e0b82",
        #         "data":{
        #             "tickers":[
        #                 {
        #                     "symbol":"ETH_BTC",
        #                     "last_price":"0.036037",
        #                     "quote_volume_24h":"4380.6660000000",
        #                     "base_volume_24h":"159.3582006712",
        #                     "high_24h":"0.036972",
        #                     "low_24h":"0.035524",
        #                     "open_24h":"0.036561",
        #                     "close_24h":"0.036037",
        #                     "best_ask":"0.036077",
        #                     "best_ask_size":"9.9500",
        #                     "best_bid":"0.035983",
        #                     "best_bid_size":"4.2792",
        #                     "fluctuation":"-0.0143",
        #                     "url":"https://www.bitmart.com/trade?symbol=ETH_BTC"
        #                 }
        #             ]
        #         }
        #     }
        #
        # swap
        #
        #      {
        #          "message":"OK",
        #          "code":1000,
        #          "trace":"4a0ebceb-d3f7-45a3-8feb-f61e230e24cd",
        #          "data":{
        #              "tickers":[
        #                  {
        #                      "contract_symbol":"DOGEUSDT",
        #                      "last_price":"0.130180",
        #                      "index_price":"0.13028635",
        #                      "last_funding_rate":"0.00002025",
        #                      "price_change_percent_24h":"-2.326",
        #                      "volume_24h":"116789313.01797258",
        #                      "url":"https://futures.bitmart.com/en?symbol=DOGEUSDT",
        #                      "high_price":"0.134520",
        #                      "low_price":"0.128570",
        #                      "legal_coin_price":"0.13017401"
        #                  }
        #              ]
        #          }
        #      }
        #
        data = self.safe_value(response, 'data', {})
        tickers = self.safe_value(data, 'tickers', [])
        # fails in naming for contract tickers 'contract_symbol'
        tickersById = None
        if market['spot']:
            tickersById = self.index_by(tickers, 'symbol')
        elif market['swap']:
            tickersById = self.index_by(tickers, 'contract_symbol')
        ticker = self.safe_value(tickersById, market['id'])
        return self.parse_ticker(ticker, market)

    def fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        see https://developer-pro.bitmart.com/en/spot/#get-ticker-of-all-pairs-v2
        :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 bitmart api endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        type = None
        market = None
        if symbols is not None:
            symbol = self.safe_value(symbols, 0)
            market = self.market(symbol)
        type, params = self.handle_market_type_and_params('fetchTickers', market, params)
        method = self.get_supported_mapping(type, {
            'spot': 'publicGetSpotV2Ticker',
            'swap': 'publicGetContractV1Tickers',
        })
        response = getattr(self, method)(params)
        data = self.safe_value(response, 'data', {})
        tickers = self.safe_value(data, 'tickers', [])
        result = {}
        for i in range(0, len(tickers)):
            ticker = self.parse_ticker(tickers[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_order_book(self, symbol, limit=None, params={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchOrderBook() does not support ' + market['type'] + ' markets, only spot markets are accepted')
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['size'] = limit  # default 50, max 200
        # request['precision'] = 4  # optional price precision / depth level whose range is defined in symbol details
        response = self.publicGetSpotV1SymbolsBook(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"8254f8fc-431d-404f-ad9a-e716339f66c7",
        #         "data":{
        #             "buys":[
        #                 {"amount":"4.7091","total":"4.71","price":"0.034047","count":"1"},
        #                 {"amount":"5.7439","total":"10.45","price":"0.034039","count":"1"},
        #                 {"amount":"2.5249","total":"12.98","price":"0.032937","count":"1"},
        #             ],
        #             "sells":[
        #                 {"amount":"41.4365","total":"41.44","price":"0.034174","count":"1"},
        #                 {"amount":"4.2317","total":"45.67","price":"0.034183","count":"1"},
        #                 {"amount":"0.3000","total":"45.97","price":"0.034240","count":"1"},
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        timestamp = self.safe_integer(data, 'timestamp')
        return self.parse_order_book(data, symbol, timestamp, 'buys', 'sells', 'price', 'amount')

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades spot( amount = count * price )
        #
        #     {
        #          "amount": "818.94",
        #          "order_time": "1637601839035",    # ETH/USDT
        #          "price": "4221.99",
        #          "count": "0.19397",
        #          "type": "buy"
        #      }
        #
        # private fetchMyTrades spot
        #
        #     {
        #         "detail_id":256348632,
        #         "order_id":2147484350,
        #         "symbol":"BTC_USDT",
        #         "create_time":1590462303000,
        #         "side":"buy",
        #         "fees":"0.00001350",
        #         "fee_coin_name":"BTC",
        #         "notional":"88.00000000",
        #         "price_avg":"8800.00",
        #         "size":"0.01000",
        #         "exec_type":"M"
        #     }
        #
        id = self.safe_string(trade, 'detail_id')
        timestamp = self.safe_integer_2(trade, 'order_time', 'create_time')
        type = None
        side = self.safe_string_lower_2(trade, 'type', 'side')
        takerOrMaker = None
        execType = self.safe_string(trade, 'exec_type')
        if execType is not None:
            takerOrMaker = 'maker' if (execType == 'M') else 'taker'
        priceString = self.safe_string(trade, 'price')
        priceString = self.safe_string(trade, 'price_avg', priceString)
        amountString = self.safe_string(trade, 'count')
        amountString = self.safe_string(trade, 'size', amountString)
        costString = self.safe_string_2(trade, 'amount', 'notional')
        orderId = self.safe_string(trade, 'order_id')
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market, '_')
        feeCostString = self.safe_string(trade, 'fees')
        fee = None
        if feeCostString is not None:
            feeCurrencyId = self.safe_string(trade, 'fee_coin_name')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            if feeCurrencyCode is None:
                feeCurrencyCode = market['base'] if (side == 'buy') else market['quote']
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
            }
        return self.safe_trade({
            'info': trade,
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': type,
            'side': side,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'takerOrMaker': takerOrMaker,
            'fee': fee,
        }, market)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchTrades() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request = {
            'symbol': market['id'],
        }
        response = self.publicGetSpotV1SymbolsTrades(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"222d74c0-8f6d-49d9-8e1b-98118c50eeba",
        #         "data":{
        #             "trades":[
        #                 {
        #                     "amount":"0.005703",
        #                     "order_time":1599652045394,
        #                     "price":"0.034029",
        #                     "count":"0.1676",
        #                     "type":"sell"
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        trades = self.safe_value(data, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        # spot
        #
        #     {
        #         "last_price":"0.034987",
        #         "timestamp":1598787420,
        #         "volume":"1.0198",
        #         "open":"0.035007",
        #         "close":"0.034987",
        #         "high":"0.035007",
        #         "low":"0.034986"
        #     }
        #
        # swap
        #
        #     {
        #         "low_price": "20090.3",
        #         "high_price": "20095.5",
        #         "open_price": "20092.6",
        #         "close_price": "20091.4",
        #         "volume": "8748",
        #         "timestamp": 1665002281
        #     }
        #
        # ws
        #
        #     [
        #         1631056350,  # timestamp
        #         '46532.83',  # oopen
        #         '46555.71',  # high
        #         '46511.41',  # low
        #         '46555.71',  # close
        #         '0.25',  # volume
        #     ]
        #
        if isinstance(ohlcv, list):
            return [
                self.safe_timestamp(ohlcv, 0),
                self.safe_number(ohlcv, 1),
                self.safe_number(ohlcv, 2),
                self.safe_number(ohlcv, 3),
                self.safe_number(ohlcv, 4),
                self.safe_number(ohlcv, 5),
            ]
        else:
            return [
                self.safe_timestamp(ohlcv, 'timestamp'),
                self.safe_number_2(ohlcv, 'open', 'open_price'),
                self.safe_number_2(ohlcv, 'high', 'high_price'),
                self.safe_number_2(ohlcv, 'low', 'low_price'),
                self.safe_number_2(ohlcv, 'close', 'close_price'),
                self.safe_number(ohlcv, 'volume'),
            ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        see https://developer-pro.bitmart.com/en/spot/#get-k-line
        see https://developer-pro.bitmart.com/en/futures/#get-k-line
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        type = market['type']
        duration = self.parse_timeframe(timeframe)
        request = {
            'symbol': market['id'],
            'step': self.safe_integer(self.timeframes, timeframe, timeframe),
        }
        maxLimit = 500
        if limit is None:
            limit = maxLimit
        limit = min(maxLimit, limit)
        now = int(self.milliseconds() / 1000)
        fromRequest = 'from' if (type == 'spot') else 'start_time'
        toRequest = 'to' if (type == 'spot') else 'end_time'
        if since is None:
            start = now - limit * duration
            request[fromRequest] = start
            request[toRequest] = now
        else:
            start = int(since / 1000) - 1
            end = self.sum(start, limit * duration)
            request[fromRequest] = start
            request[toRequest] = min(end, now)
        method = 'publicGetSpotV1SymbolsKline'
        if type == 'swap':
            method = 'publicGetContractPublicKline'
        response = getattr(self, method)(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"80d86378-ab4e-4c70-819e-b42146cf87ad",
        #         "data":{
        #             "klines":[
        #                 {"last_price":"0.034987","timestamp":1598787420,"volume":"1.0198","open":"0.035007","close":"0.034987","high":"0.035007","low":"0.034986"},
        #                 {"last_price":"0.034986","timestamp":1598787480,"volume":"0.3959","open":"0.034982","close":"0.034986","high":"0.034986","low":"0.034980"},
        #                 {"last_price":"0.034978","timestamp":1598787540,"volume":"0.3259","open":"0.034987","close":"0.034978","high":"0.034987","low":"0.034977"},
        #             ]
        #         }
        #     }
        #
        # swap
        #
        #     {
        #         "code": 1000,
        #         "message": "Ok",
        #         "data": [
        #             {
        #                 "low_price": "20090.3",
        #                 "high_price": "20095.5",
        #                 "open_price": "20092.6",
        #                 "close_price": "20091.4",
        #                 "volume": "8748",
        #                 "timestamp": 1665002281
        #             },
        #             ...
        #         ],
        #         "trace": "96c989db-e0f5-46f5-bba6-60cfcbde699b"
        #     }
        #
        data = self.safe_value(response, 'data', {})
        klines = self.safe_value(data, 'klines', [])
        ohlcv = klines if (type == 'spot') else data
        return self.parse_ohlcvs(ohlcv, market, timeframe, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all trades made by the user
        :param str symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchMyTrades() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        options = self.safe_value(self.options, 'fetchMyTrades', {})
        defaultLimit = self.safe_integer(options, 'limit', 200)
        if limit is None:
            limit = defaultLimit
        request = {
            'symbol': market['id'],
            'N': limit,
        }
        response = self.privateGetSpotV2Trades(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"a06a5c53-8e6f-42d6-8082-2ff4718d221c",
        #         "data":{
        #             "current_page":1,
        #             "trades":[
        #                 {
        #                     "detail_id":256348632,
        #                     "order_id":2147484350,
        #                     "symbol":"BTC_USDT",
        #                     "create_time":1590462303000,
        #                     "side":"buy",
        #                     "fees":"0.00001350",
        #                     "fee_coin_name":"BTC",
        #                     "notional":"88.00000000",
        #                     "price_avg":"8800.00",
        #                     "size":"0.01000",
        #                     "exec_type":"M"
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        trades = self.safe_value(data, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        """
        fetch all the trades made from a single order
        :param str id: order id
        :param str symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades to retrieve
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrderTrades() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchOrderTrades() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        options = self.safe_value(self.options, 'fetchOrderTrades', {})
        defaultLimit = self.safe_integer(options, 'limit', 200)
        if limit is None:
            limit = defaultLimit
        request = {
            'symbol': market['id'],
            'order_id': id,
            'N': limit,
        }
        response = self.privateGetSpotV2Trades(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"a06a5c53-8e6f-42d6-8082-2ff4718d221c",
        #         "data":{
        #             "current_page":1,
        #             "trades":[
        #                 {
        #                     "detail_id":256348632,
        #                     "order_id":2147484350,
        #                     "symbol":"BTC_USDT",
        #                     "create_time":1590462303000,
        #                     "side":"buy",
        #                     "fees":"0.00001350",
        #                     "fee_coin_name":"BTC",
        #                     "notional":"88.00000000",
        #                     "price_avg":"8800.00",
        #                     "size":"0.01000",
        #                     "exec_type":"M"
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        trades = self.safe_value(data, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_balance(self, response, marketType):
        data = self.safe_value(response, 'data', {})
        wallet = None
        if marketType == 'swap':
            wallet = self.safe_value(response, 'data', [])
        elif marketType == 'margin':
            wallet = self.safe_value(data, 'symbols', [])
        else:
            wallet = self.safe_value(data, 'wallet', [])
        result = {'info': response}
        if marketType == 'margin':
            for i in range(0, len(wallet)):
                entry = wallet[i]
                marketId = self.safe_string(entry, 'symbol')
                symbol = self.safe_symbol(marketId, None, '_')
                base = self.safe_value(entry, 'base', {})
                quote = self.safe_value(entry, 'quote', {})
                baseCode = self.safe_currency_code(self.safe_string(base, 'currency'))
                quoteCode = self.safe_currency_code(self.safe_string(quote, 'currency'))
                subResult = {}
                subResult[baseCode] = self.parse_balance_helper(base)
                subResult[quoteCode] = self.parse_balance_helper(quote)
                result[symbol] = self.safe_balance(subResult)
            return result
        else:
            for i in range(0, len(wallet)):
                balance = wallet[i]
                currencyId = self.safe_string_2(balance, 'id', 'currency')
                currencyId = self.safe_string(balance, 'coin_code', currencyId)
                code = self.safe_currency_code(currencyId)
                account = self.account()
                account['free'] = self.safe_string_2(balance, 'available', 'available_balance')
                account['used'] = self.safe_string_2(balance, 'frozen', 'frozen_balance')
                result[code] = account
            return self.safe_balance(result)

    def parse_balance_helper(self, entry):
        account = self.account()
        account['used'] = self.safe_string(entry, 'frozen')
        account['free'] = self.safe_string(entry, 'available')
        account['total'] = self.safe_string(entry, 'total_asset')
        debt = self.safe_string(entry, 'borrow_unpaid')
        interest = self.safe_string(entry, 'interest_unpaid')
        account['debt'] = Precise.string_add(debt, interest)
        return account

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        see https://developer-pro.bitmart.com/en/spot/#get-spot-wallet-balance
        see https://developer-pro.bitmart.com/en/futures/#get-contract-assets-detail
        see https://developer-pro.bitmart.com/en/spot/#get-account-balance
        see https://developer-pro.bitmart.com/en/spot/#get-margin-account-details-isolated
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        self.load_markets()
        marketType = None
        marketType, params = self.handle_market_type_and_params('fetchBalance', None, params)
        method = self.get_supported_mapping(marketType, {
            'spot': 'privateGetSpotV1Wallet',
            'swap': 'privateGetContractPrivateAssetsDetail',
            'account': 'privateGetAccountV1Wallet',
            'margin': 'privateGetSpotV1MarginIsolatedAccount',
        })
        marginMode = self.safe_string(params, 'marginMode')
        isMargin = self.safe_value(params, 'margin', False)
        params = self.omit(params, ['margin', 'marginMode'])
        if marginMode is not None or isMargin:
            method = 'privateGetSpotV1MarginIsolatedAccount'
            marketType = 'margin'
        response = getattr(self, method)(params)
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"39069916-72f9-44c7-acde-2ad5afd21cad",
        #         "data":{
        #             "wallet":[
        #                 {"id":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000"},
        #                 {"id":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000"},
        #                 {"id":"BMX","name":"BitMart Token","available":"0.00000000","frozen":"0.00000000"}
        #             ]
        #         }
        #     }
        #
        # account
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"5c3b7fc7-93b2-49ef-bb59-7fdc56915b59",
        #         "data":{
        #             "wallet":[
        #                 {"currency":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000"},
        #                 {"currency":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000"}
        #             ]
        #         }
        #     }
        #
        # swap
        #
        #     {
        #         "code": 1000,
        #         "message": "Ok",
        #         "data": [
        #             {
        #                 "currency": "USDT",
        #                 "available_balance": "0",
        #                 "frozen_balance": "0",
        #                 "unrealized": "0",
        #                 "equity": "0",
        #                 "position_deposit": "0"
        #             },
        #             ...
        #         ],
        #         "trace": "f9da3a39-cf45-42e7-914d-294f565dfc33"
        #     }
        #
        # margin
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "61dd6ab265c04064b72d8bc9b205f741.71.16701055600915302",
        #         "data": {
        #             "symbols": [
        #                 {
        #                     "symbol": "BTC_USDT",
        #                     "risk_rate": "999.00",
        #                     "risk_level": "1",
        #                     "buy_enabled": False,
        #                     "sell_enabled": False,
        #                     "liquidate_price": null,
        #                     "liquidate_rate": "1.15",
        #                     "base": {
        #                         "currency": "BTC",
        #                         "borrow_enabled": True,
        #                         "borrowed": "0.00000000",
        #                         "available": "0.00000000",
        #                         "frozen": "0.00000000",
        #                         "net_asset": "0.00000000",
        #                         "net_assetBTC": "0.00000000",
        #                         "total_asset": "0.00000000",
        #                         "borrow_unpaid": "0.00000000",
        #                         "interest_unpaid": "0.00000000"
        #                     },
        #                     "quote": {
        #                         "currency": "USDT",
        #                         "borrow_enabled": True,
        #                         "borrowed": "0.00000000",
        #                         "available": "20.00000000",
        #                         "frozen": "0.00000000",
        #                         "net_asset": "20.00000000",
        #                         "net_assetBTC": "0.00118008",
        #                         "total_asset": "20.00000000",
        #                         "borrow_unpaid": "0.00000000",
        #                         "interest_unpaid": "0.00000000"
        #                     }
        #                 }
        #             ]
        #         }
        #     }
        #
        return self.parse_balance(response, marketType)

    def parse_trading_fee(self, fee, market=None):
        #
        #     {
        #         symbol: 'ETH_USDT',
        #         taker_fee_rate: '0.0025',
        #         maker_fee_rate: '0.0025'
        #     }
        #
        marketId = self.safe_string(fee, 'symbol')
        symbol = self.safe_symbol(marketId)
        return {
            'info': fee,
            'symbol': symbol,
            'maker': self.safe_number(fee, 'maker_fee_rate'),
            'taker': self.safe_number(fee, 'taker_fee_rate'),
        }

    def fetch_trading_fee(self, symbol, params={}):
        """
        fetch the trading fees for a market
        :param str symbol: unified market symbol
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `fee structure <https://docs.ccxt.com/en/latest/manual.html#fee-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchTradingFee() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request = {
            'symbol': market['id'],
        }
        response = self.privateGetSpotV1TradeFee(self.extend(request, params))
        #
        #     {
        #         message: 'OK',
        #         code: '1000',
        #         trace: '5a6f1e40-37fe-4849-a494-03279fadcc62',
        #         data: {
        #             symbol: 'ETH_USDT',
        #             taker_fee_rate: '0.0025',
        #             maker_fee_rate: '0.0025'
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_trading_fee(data)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "order_id": 2707217580
        #     }
        #
        # cancelOrder
        #
        #     '2707217580'  # order id
        #
        # spot fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "order_id":1736871726781,
        #         "symbol":"BTC_USDT",
        #         "create_time":1591096004000,
        #         "side":"sell",
        #         "type":"market",  # limit, market, limit_maker, ioc
        #         "price":"0.00",
        #         "price_avg":"0.00",
        #         "size":"0.02000",
        #         "notional":"0.00000000",
        #         "filled_notional":"0.00000000",
        #         "filled_size":"0.00000",
        #         "status":"8"
        #     }
        #
        id = None
        if isinstance(order, str):
            id = order
            order = {}
        id = self.safe_string(order, 'order_id', id)
        timestamp = self.safe_integer(order, 'create_time')
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market, '_')
        status = None
        if market is not None:
            status = self.parse_order_status_by_type(market['type'], self.safe_string(order, 'status'))
        amount = self.safe_string(order, 'size')
        filled = self.safe_string(order, 'filled_size')
        average = self.safe_string(order, 'price_avg')
        price = self.safe_string(order, 'price')
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'type')
        timeInForce = None
        postOnly = None
        if type == 'limit_maker':
            type = 'limit'
            postOnly = True
            timeInForce = 'PO'
        if type == 'ioc':
            type = 'limit'
            timeInForce = 'IOC'
        return self.safe_order({
            'id': id,
            'clientOrderId': None,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'amount': amount,
            'cost': None,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': None,
            'trades': None,
        }, market)

    def parse_order_status_by_type(self, type, status):
        statusesByType = {
            'spot': {
                '1': 'failed',  # Order failure
                '2': 'open',  # Placing order
                '3': 'failed',  # Order failure, Freeze failure
                '4': 'open',  # Order success, Pending for fulfilment
                '5': 'open',  # Partially filled
                '6': 'closed',  # Fully filled
                '7': 'canceling',  # Canceling
                '8': 'canceled',  # Canceled
            },
            'swap': {
                '1': 'open',  # Submitting
                '2': 'open',  # Commissioned
                '4': 'closed',  # Completed
            },
        }
        statuses = self.safe_value(statusesByType, type, {})
        return self.safe_string(statuses, status, status)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        see https://developer-pro.bitmart.com/en/spot/#place-spot-order
        see https://developer-pro.bitmart.com/en/spot/#place-margin-order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float|None price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the bitmart api endpoint
        :param str|None params['marginMode']: 'cross' or 'isolated'
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {}
        timeInForce = self.safe_string(params, 'timeInForce')
        if timeInForce == 'FOK':
            raise InvalidOrder(self.id + ' createOrder() only accepts timeInForce parameter values of IOC or PO')
        isMarketOrder = type == 'market'
        postOnly = self.is_post_only(isMarketOrder, type == 'limit_maker', params)
        params = self.omit(params, ['timeInForce', 'postOnly'])
        ioc = ((timeInForce == 'IOC') or (type == 'ioc'))
        isLimitOrder = (type == 'limit') or postOnly or ioc
        method = None
        if market['spot']:
            request['symbol'] = market['id']
            request['side'] = side
            request['type'] = type
            method = 'privatePostSpotV2SubmitOrder'
            if isLimitOrder:
                request['size'] = self.amount_to_precision(symbol, amount)
                request['price'] = self.price_to_precision(symbol, price)
            elif isMarketOrder:
                # for market buy it requires the amount of quote currency to spend
                if side == 'buy':
                    notional = self.safe_number(params, 'notional')
                    createMarketBuyOrderRequiresPrice = self.safe_value(self.options, 'createMarketBuyOrderRequiresPrice', True)
                    if createMarketBuyOrderRequiresPrice:
                        if price is not None:
                            if notional is None:
                                amountString = self.number_to_string(amount)
                                priceString = self.number_to_string(price)
                                notional = self.parse_number(Precise.string_mul(amountString, priceString))
                        elif notional is None:
                            raise InvalidOrder(self.id + " createOrder() requires the price argument with 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 and supply the total cost value in the 'amount' argument or in the 'notional' extra parameter(the exchange-specific behaviour)")
                    else:
                        notional = amount if (notional is None) else notional
                    request['notional'] = self.decimal_to_precision(notional, TRUNCATE, market['precision']['price'], self.precisionMode)
                elif side == 'sell':
                    request['size'] = self.amount_to_precision(symbol, amount)
        elif market['swap']:
            raise NotSupported(self.id + ' createOrder() does not accept swap orders, only spot orders are allowed')
        if postOnly:
            request['type'] = 'limit_maker'
        if ioc:
            request['type'] = 'ioc'
        marginMode, query = self.handle_margin_mode_and_params('createOrder', params)
        if marginMode is not None:
            method = 'privatePostSpotV1MarginSubmitOrder'
        response = getattr(self, method)(self.extend(request, query))
        #
        # spot and margin
        #
        #     {
        #         "code": 1000,
        #         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
        #         "message": "OK",
        #         "data": {
        #             "order_id": 2707217580
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        order = self.parse_order(data, market)
        return self.extend(order, {
            'type': type,
            'side': side,
            'amount': amount,
            'price': price,
        })

    def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' cancelOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request = {
            'order_id': str(id),
            'symbol': market['id'],
        }
        response = self.privatePostSpotV3CancelOrder(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "code": 1000,
        #         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
        #         "message": "OK",
        #         "data": {
        #             "result": True
        #         }
        #     }
        #
        # spot alternative
        #
        #     {
        #         "code": 1000,
        #         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
        #         "message": "OK",
        #         "data": True
        #     }
        #
        data = self.safe_value(response, 'data')
        if data is True:
            return self.parse_order(id, market)
        succeeded = self.safe_value(data, 'succeed')
        if succeeded is not None:
            id = self.safe_string(succeeded, 0)
            if id is None:
                raise InvalidOrder(self.id + ' cancelOrder() failed to cancel ' + symbol + ' order id ' + id)
        else:
            result = self.safe_value(data, 'result')
            if not result:
                raise InvalidOrder(self.id + ' cancelOrder() ' + symbol + ' order id ' + id + ' is filled or canceled')
        order = self.parse_order(id, market)
        return self.extend(order, {'id': id})

    def cancel_all_orders(self, symbol=None, params={}):
        """
        cancel all open orders in a market
        :param str symbol: unified market symbol of the market to cancel orders in
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument')
        side = self.safe_string(params, 'side')
        if side is None:
            raise ArgumentsRequired(self.id + " cancelAllOrders() requires a `side` parameter('buy' or 'sell')")
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' cancelAllOrders() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request = {
            'symbol': market['id'],
            'side': side,  # 'buy' or 'sell'
        }
        response = self.privatePostSpotV1CancelOrders(self.extend(request, params))
        #
        #     {
        #         "code": 1000,
        #         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
        #         "message": "OK",
        #         "data": {}
        #     }
        #
        return response

    def fetch_orders_by_status(self, status, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrdersByStatus() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchOrdersByStatus() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request = {
            'symbol': market['id'],
            'offset': 1,  # max offset * limit < 500
            'N': 100,  # max limit is 100
        }
        if status == 'open':
            request['status'] = 9
        elif status == 'closed':
            request['status'] = 6
        elif status == 'canceled':
            request['status'] = 8
        else:
            request['status'] = status
        response = self.privateGetSpotV3Orders(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"70e7d427-7436-4fb8-8cdd-97e1f5eadbe9",
        #         "data":{
        #             "current_page":1,
        #             "orders":[
        #                 {
        #                     "order_id":2147601241,
        #                     "symbol":"BTC_USDT",
        #                     "create_time":1591099963000,
        #                     "side":"sell",
        #                     "type":"limit",
        #                     "price":"9000.00",
        #                     "price_avg":"0.00",
        #                     "size":"1.00000",
        #                     "notional":"9000.00000000",
        #                     "filled_notional":"0.00000000",
        #                     "filled_size":"0.00000",
        #                     "status":"4"
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        orders = self.safe_value(data, 'orders', [])
        return self.parse_orders(orders, market, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        :param str symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch open orders for
        :param int|None limit: the maximum number of  open orders structures to retrieve
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        return self.fetch_orders_by_status('open', symbol, since, limit, params)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple closed orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        return self.fetch_orders_by_status('closed', symbol, since, limit, params)

    def fetch_canceled_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple canceled orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int|None since: timestamp in ms of the earliest order, default is None
        :param int|None limit: max number of orders to return, default is None
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        return self.fetch_orders_by_status('canceled', symbol, since, limit, params)

    def fetch_order(self, id, symbol=None, params={}):
        """
        fetches information on an order made by the user
        :param str symbol: unified symbol of the market the order was made in
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' fetchOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        if not isinstance(id, str):
            id = str(id)
        request = {
            'symbol': market['id'],
            'order_id': id,
        }
        response = self.privateGetSpotV2OrderDetail(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"a27c2cb5-ead4-471d-8455-1cfeda054ea6",
        #         "data": {
        #             "order_id":1736871726781,
        #             "symbol":"BTC_USDT",
        #             "create_time":1591096004000,
        #             "side":"sell",
        #             "type":"market",
        #             "price":"0.00",
        #             "price_avg":"0.00",
        #             "size":"0.02000",
        #             "notional":"0.00000000",
        #             "filled_notional":"0.00000000",
        #             "filled_size":"0.00000",
        #             "status":"8"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.parse_order(data, market)

    def fetch_deposit_address(self, code, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        if code == 'USDT':
            defaultNetworks = self.safe_value(self.options, 'defaultNetworks')
            defaultNetwork = self.safe_string_upper(defaultNetworks, code)
            networks = self.safe_value(self.options, 'networks', {})
            network = self.safe_string_upper(params, 'network', defaultNetwork)  # self line allows the user to specify either ERC20 or ETH
            network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
            if network is not None:
                request['currency'] += '-' + network  # when network the currency need to be changed to currency + '-' + network https://developer-pro.bitmart.com/en/account/withdraw_apply.html on the end of page
                params = self.omit(params, 'network')
        response = self.privateGetAccountV1DepositAddress(self.extend(request, params))
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"0e6edd79-f77f-4251-abe5-83ba75d06c1a",
        #         "data":{
        #             "currency":"USDT-TRC20",
        #             "chain":"USDT-TRC20",
        #             "address":"TGR3ghy2b5VLbyAYrmiE15jasR6aPHTvC5",
        #             "address_memo":""
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        address = self.safe_string(data, 'address')
        tag = self.safe_string(data, 'address_memo')
        chain = self.safe_string(data, 'chain')
        network = None
        if chain is not None:
            parts = chain.split('-')
            networkId = self.safe_string(parts, 1)
            network = self.safe_network(networkId)
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': network,
            'info': response,
        }

    def safe_network(self, networkId):
        # TODO: parse
        return networkId

    def withdraw(self, code, amount, address, tag=None, params={}):
        """
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str|None tag:
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
            'amount': amount,
            'destination': 'To Digital Address',  # To Digital Address, To Binance, To OKEX
            'address': address,
        }
        if tag is not None:
            request['address_memo'] = tag
        if code == 'USDT':
            defaultNetworks = self.safe_value(self.options, 'defaultNetworks')
            defaultNetwork = self.safe_string_upper(defaultNetworks, code)
            networks = self.safe_value(self.options, 'networks', {})
            network = self.safe_string_upper(params, 'network', defaultNetwork)  # self line allows the user to specify either ERC20 or ETH
            network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
            if network is not None:
                request['currency'] += '-' + network  # when network the currency need to be changed to currency + '-' + network https://developer-pro.bitmart.com/en/account/withdraw_apply.html on the end of page
                params = self.omit(params, 'network')
        response = self.privatePostAccountV1WithdrawApply(self.extend(request, params))
        #
        #     {
        #         "code": 1000,
        #         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
        #         "message": "OK",
        #         "data": {
        #             "withdraw_id": "121212"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        transaction = self.parse_transaction(data, currency)
        return self.extend(transaction, {
            'code': code,
            'address': address,
            'tag': tag,
        })

    def fetch_transactions_by_type(self, type, code=None, since=None, limit=None, params={}):
        self.load_markets()
        if limit is None:
            limit = 50  # max 50
        request = {
            'operation_type': type,  # deposit or withdraw
            'offset': 1,
            'N': limit,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if code == 'USDT':
            defaultNetworks = self.safe_value(self.options, 'defaultNetworks')
            defaultNetwork = self.safe_string_upper(defaultNetworks, code)
            networks = self.safe_value(self.options, 'networks', {})
            network = self.safe_string_upper(params, 'network', defaultNetwork)  # self line allows the user to specify either ERC20 or ETH
            network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
            if network is not None:
                request['currency'] += '-' + network  # when network the currency need to be changed to currency + '-' + network https://developer-pro.bitmart.com/en/account/withdraw_apply.html on the end of page
                currency['code'] = request['currency']  # update currency code to filter
                params = self.omit(params, 'network')
        response = self.privateGetAccountV2DepositWithdrawHistory(self.extend(request, params))
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"142bf92a-fc50-4689-92b6-590886f90b97",
        #         "data":{
        #             "records":[
        #                 {
        #                     "withdraw_id":"1679952",
        #                     "deposit_id":"",
        #                     "operation_type":"withdraw",
        #                     "currency":"BMX",
        #                     "apply_time":1588867374000,
        #                     "arrival_amount":"59.000000000000",
        #                     "fee":"1.000000000000",
        #                     "status":0,
        #                     "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
        #                     "address_memo":"",
        #                     "tx_id":""
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        records = self.safe_value(data, 'records', [])
        return self.parse_transactions(records, currency, since, limit)

    def fetch_deposit(self, id, code=None, params={}):
        """
        fetch information on a deposit
        :param str id: deposit id
        :param str|None code: not used by bitmart fetchDeposit()
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        request = {
            'id': id,
        }
        response = self.privateGetAccountV1DepositWithdrawDetail(self.extend(request, params))
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"f7f74924-14da-42a6-b7f2-d3799dd9a612",
        #         "data":{
        #             "record":{
        #                 "withdraw_id":"",
        #                 "deposit_id":"1679952",
        #                 "operation_type":"deposit",
        #                 "currency":"BMX",
        #                 "apply_time":1588867374000,
        #                 "arrival_amount":"59.000000000000",
        #                 "fee":"1.000000000000",
        #                 "status":0,
        #                 "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
        #                 "address_memo":"",
        #                 "tx_id":""
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        record = self.safe_value(data, 'record', {})
        return self.parse_transaction(record)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        """
        fetch all deposits made to an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch deposits for
        :param int|None limit: the maximum number of deposits structures to retrieve
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        return self.fetch_transactions_by_type('deposit', code, since, limit, params)

    def fetch_withdrawal(self, id, code=None, params={}):
        """
        fetch data on a currency withdrawal via the withdrawal id
        :param str id: withdrawal id
        :param str|None code: not used by bitmart.fetchWithdrawal
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        self.load_markets()
        request = {
            'id': id,
        }
        response = self.privateAccountGetDepositWithdrawDetail(self.extend(request, params))
        #
        #     {
        #         "message":"OK",
        #         "code":1000,
        #         "trace":"f7f74924-14da-42a6-b7f2-d3799dd9a612",
        #         "data":{
        #             "record":{
        #                 "withdraw_id":"1679952",
        #                 "deposit_id":"",
        #                 "operation_type":"withdraw",
        #                 "currency":"BMX",
        #                 "apply_time":1588867374000,
        #                 "arrival_amount":"59.000000000000",
        #                 "fee":"1.000000000000",
        #                 "status":0,
        #                 "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
        #                 "address_memo":"",
        #                 "tx_id":""
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        record = self.safe_value(data, 'record', {})
        return self.parse_transaction(record)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        """
        fetch all withdrawals made from an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch withdrawals for
        :param int|None limit: the maximum number of withdrawals structures to retrieve
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        return self.fetch_transactions_by_type('withdraw', code, since, limit, params)

    def parse_transaction_status(self, status):
        statuses = {
            '0': 'pending',  # Create
            '1': 'pending',  # Submitted, waiting for withdrawal
            '2': 'pending',  # Processing
            '3': 'ok',  # Success
            '4': 'canceled',  # Cancel
            '5': 'failed',  # Fail
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # withdraw
        #
        #     {
        #         "withdraw_id": "121212"
        #     }
        #
        # fetchDeposits, fetchWithdrawals, fetchWithdrawal
        #
        #     {
        #         "withdraw_id":"1679952",
        #         "deposit_id":"",
        #         "operation_type":"withdraw",
        #         "currency":"BMX",
        #         "apply_time":1588867374000,
        #         "arrival_amount":"59.000000000000",
        #         "fee":"1.000000000000",
        #         "status":0,
        #         "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
        #         "address_memo":"",
        #         "tx_id":""
        #     }
        #
        id = None
        withdrawId = self.safe_string(transaction, 'withdraw_id')
        depositId = self.safe_string(transaction, 'deposit_id')
        type = None
        if (withdrawId is not None) and (withdrawId != ''):
            type = 'withdraw'
            id = withdrawId
        elif (depositId is not None) and (depositId != ''):
            type = 'deposit'
            id = depositId
        amount = self.safe_number(transaction, 'arrival_amount')
        timestamp = self.safe_integer(transaction, 'apply_time')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        feeCost = self.safe_number(transaction, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': code,
            }
        txid = self.safe_string(transaction, 'tx_id')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'address_memo')
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'network': None,
            'address': address,
            'addressFrom': None,
            'addressTo': None,
            'tag': tag,
            'tagFrom': None,
            'tagTo': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp if (timestamp != 0) else None,
            'datetime': self.iso8601(timestamp) if (timestamp != 0) else None,
            'fee': fee,
        }

    def repay_margin(self, code, amount, symbol=None, params={}):
        """
        repay borrowed margin and interest
        see https://developer-pro.bitmart.com/en/spot/#margin-repay-isolated
        :param str code: unified currency code of the currency to repay
        :param str amount: the amount to repay
        :param str symbol: unified market symbol
        :param dict params: extra parameters specific to the bitmart api endpoint
        :param str|None params['marginMode']: 'isolated' is the default and 'cross' is unavailable
        :returns dict: a `margin loan structure <https://docs.ccxt.com/en/latest/manual.html#margin-loan-structure>`
        """
        self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' repayMargin() requires a symbol argument')
        market = self.market(symbol)
        currency = self.currency(code)
        request = {
            'symbol': market['id'],
            'currency': currency['id'],
            'amount': self.currency_to_precision(code, amount),
        }
        params = self.omit(params, 'marginMode')
        response = self.privatePostSpotV1MarginIsolatedRepay(self.extend(request, params))
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "b0a60b4c-e986-4b54-a190-8f7c05ddf685",
        #         "data": {
        #             "repay_id": "2afcc16d99bd4707818c5a355dc89bed"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        transaction = self.parse_margin_loan(data, currency)
        return self.extend(transaction, {
            'amount': amount,
            'symbol': symbol,
        })

    def borrow_margin(self, code, amount, symbol=None, params={}):
        """
        create a loan to borrow margin
        see https://developer-pro.bitmart.com/en/spot/#margin-borrow-isolated
        :param str code: unified currency code of the currency to borrow
        :param str amount: the amount to borrow
        :param str symbol: unified market symbol
        :param dict params: extra parameters specific to the bitmart api endpoint
        :param str|None params['marginMode']: 'isolated' is the default and 'cross' is unavailable
        :returns dict: a `margin loan structure <https://docs.ccxt.com/en/latest/manual.html#margin-loan-structure>`
        """
        self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' borrowMargin() requires a symbol argument')
        market = self.market(symbol)
        currency = self.currency(code)
        request = {
            'symbol': market['id'],
            'currency': currency['id'],
            'amount': self.currency_to_precision(code, amount),
        }
        params = self.omit(params, 'marginMode')
        response = self.privatePostSpotV1MarginIsolatedBorrow(self.extend(request, params))
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "e6fda683-181e-4e78-ac9c-b27c4c8ba035",
        #         "data": {
        #             "borrow_id": "629a7177a4ed4cf09869c6a4343b788c"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        transaction = self.parse_margin_loan(data, currency)
        return self.extend(transaction, {
            'amount': amount,
            'symbol': symbol,
        })

    def parse_margin_loan(self, info, currency=None):
        #
        # borrowMargin
        #
        #     {
        #         "borrow_id": "629a7177a4ed4cf09869c6a4343b788c",
        #     }
        #
        # repayMargin
        #
        #     {
        #         "repay_id": "2afcc16d99bd4707818c5a355dc89bed",
        #     }
        #
        timestamp = self.milliseconds()
        return {
            'id': self.safe_string_2(info, 'borrow_id', 'repay_id'),
            'currency': self.safe_currency_code(None, currency),
            'amount': None,
            'symbol': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'info': info,
        }

    def fetch_borrow_rate(self, code, params={}):
        """
        fetch the rate of interest to borrow a currency for margin trading
        see https://developer-pro.bitmart.com/en/spot/#get-trading-pair-borrowing-rate-and-amount
        :param str code: unified currency code
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a `borrow rate structure <https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure>`
        """
        self.load_markets()
        market = None
        if code in self.markets:
            market = self.market(code)
        else:
            defaultSettle = self.safe_string(self.options, 'defaultSettle', 'USDT')
            if code == 'USDT':
                market = self.market('BTC' + '/' + defaultSettle)
            else:
                market = self.market(code + '/' + defaultSettle)
        request = {
            'symbol': market['id'],
        }
        response = self.privateGetSpotV1MarginIsolatedPairs(self.extend(request, params))
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "0985a130-a5ae-4fc1-863f-4704e214f585",
        #         "data": {
        #             "symbols": [
        #                 {
        #                     "symbol": "BTC_USDT",
        #                     "max_leverage": "5",
        #                     "symbol_enabled": True,
        #                     "base": {
        #                         "currency": "BTC",
        #                         "daily_interest": "0.00055000",
        #                         "hourly_interest": "0.00002291",
        #                         "max_borrow_amount": "2.00000000",
        #                         "min_borrow_amount": "0.00000001",
        #                         "borrowable_amount": "0.00670810"
        #                     },
        #                     "quote": {
        #                         "currency": "USDT",
        #                         "daily_interest": "0.00055000",
        #                         "hourly_interest": "0.00002291",
        #                         "max_borrow_amount": "50000.00000000",
        #                         "min_borrow_amount": "0.00000001",
        #                         "borrowable_amount": "135.12575038"
        #                     }
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        symbols = self.safe_value(data, 'symbols', [])
        currency = market['quote'] if (code == 'USDT') else market['base']
        return self.parse_borrow_rate(symbols, currency)

    def parse_borrow_rate(self, info, currency=None):
        #
        #     {
        #         "symbol": "BTC_USDT",
        #         "max_leverage": "5",
        #         "symbol_enabled": True,
        #         "base": {
        #             "currency": "BTC",
        #             "daily_interest": "0.00055000",
        #             "hourly_interest": "0.00002291",
        #             "max_borrow_amount": "2.00000000",
        #             "min_borrow_amount": "0.00000001",
        #             "borrowable_amount": "0.00670810"
        #         },
        #         "quote": {
        #             "currency": "USDT",
        #             "daily_interest": "0.00055000",
        #             "hourly_interest": "0.00002291",
        #             "max_borrow_amount": "50000.00000000",
        #             "min_borrow_amount": "0.00000001",
        #             "borrowable_amount": "135.12575038"
        #         }
        #     }
        #
        timestamp = self.milliseconds()
        currencyData = self.safe_value(info[0], 'quote', {}) if (currency == 'USDT') else self.safe_value(info[0], 'base', {})
        return {
            'currency': self.safe_currency_code(currency),
            'rate': self.safe_number(currencyData, 'hourly_interest'),
            'period': 3600000,  # 1-Hour
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'info': info,
        }

    def fetch_borrow_rates(self, params={}):
        """
        fetch the borrow interest rates of all currencies, currently only works for isolated margin
        see https://developer-pro.bitmart.com/en/spot/#get-trading-pair-borrowing-rate-and-amount
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns dict: a list of `borrow rate structures <https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure>`
        """
        self.load_markets()
        response = self.privateGetSpotV1MarginIsolatedPairs(params)
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "0985a130-a5ae-4fc1-863f-4704e214f585",
        #         "data": {
        #             "symbols": [
        #                 {
        #                     "symbol": "BTC_USDT",
        #                     "max_leverage": "5",
        #                     "symbol_enabled": True,
        #                     "base": {
        #                         "currency": "BTC",
        #                         "daily_interest": "0.00055000",
        #                         "hourly_interest": "0.00002291",
        #                         "max_borrow_amount": "2.00000000",
        #                         "min_borrow_amount": "0.00000001",
        #                         "borrowable_amount": "0.00670810"
        #                     },
        #                     "quote": {
        #                         "currency": "USDT",
        #                         "daily_interest": "0.00055000",
        #                         "hourly_interest": "0.00002291",
        #                         "max_borrow_amount": "50000.00000000",
        #                         "min_borrow_amount": "0.00000001",
        #                         "borrowable_amount": "135.12575038"
        #                     }
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        symbols = self.safe_value(data, 'symbols', [])
        return self.parse_borrow_rates(symbols, None)

    def parse_borrow_rates(self, info, codeKey):
        #
        #     {
        #         "symbol": "BTC_USDT",
        #         "max_leverage": "5",
        #         "symbol_enabled": True,
        #         "base": {
        #             "currency": "BTC",
        #             "daily_interest": "0.00055000",
        #             "hourly_interest": "0.00002291",
        #             "max_borrow_amount": "2.00000000",
        #             "min_borrow_amount": "0.00000001",
        #             "borrowable_amount": "0.00670810"
        #         },
        #         "quote": {
        #             "currency": "USDT",
        #             "daily_interest": "0.00055000",
        #             "hourly_interest": "0.00002291",
        #             "max_borrow_amount": "50000.00000000",
        #             "min_borrow_amount": "0.00000001",
        #             "borrowable_amount": "135.12575038"
        #         }
        #     }
        #
        timestamp = self.milliseconds()
        rates = []
        for i in range(0, len(info)):
            entry = info[i]
            base = self.safe_value(entry, 'base', {})
            rates.append({
                'currency': self.safe_currency_code(self.safe_string(base, 'currency')),
                'rate': self.safe_number(base, 'hourly_interest'),
                'period': 3600000,  # 1-Hour
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
                'info': entry,
            })
        return rates

    def transfer(self, code, amount, fromAccount, toAccount, params={}):
        """
        transfer currency internally between wallets on the same account, currently only supports transfer between spot and margin
        see https://developer-pro.bitmart.com/en/spot/#margin-asset-transfer
        :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 bitmart api endpoint
        :returns dict: a `transfer structure <https://docs.ccxt.com/en/latest/manual.html#transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        amountToPrecision = self.currency_to_precision(code, amount)
        request = {
            'amount': amountToPrecision,
            'currency': currency['id'],
        }
        fromId = self.convert_type_to_account(fromAccount)
        toId = self.convert_type_to_account(toAccount)
        if fromAccount == 'spot':
            request['side'] = 'in'
            request['symbol'] = toId
        elif toAccount == 'spot':
            request['side'] = 'out'
            request['symbol'] = fromId
        else:
            raise ArgumentsRequired(self.id + ' transfer() requires either fromAccount or toAccount to be spot')
        response = self.privatePostSpotV1MarginIsolatedTransfer(self.extend(request, params))
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "b26cecec-ef5a-47d9-9531-2bd3911d3d55",
        #         "data": {
        #             "transfer_id": "ca90d97a621e47d49774f19af6b029f5"
        #         }
        #     }
        #
        return self.extend(self.parse_transfer(response, currency), {
            'amount': self.parse_number(amountToPrecision),
            'fromAccount': fromAccount,
            'toAccount': toAccount,
        })

    def parse_transfer_status(self, status):
        statuses = {
            '1000': 'ok',
            'OK': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transfer(self, transfer, currency=None):
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "b26cecec-ef5a-47d9-9531-2bd3911d3d55",
        #         "data": {
        #             "transfer_id": "ca90d97a621e47d49774f19af6b029f5"
        #         }
        #     }
        #
        data = self.safe_value(transfer, 'data', {})
        return {
            'id': self.safe_string(data, 'transfer_id'),
            'timestamp': None,
            'datetime': None,
            'currency': self.safe_currency_code(None, currency),
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': self.parse_transfer_status(self.safe_string_2(transfer, 'code', 'message')),
        }

    def fetch_borrow_interest(self, code=None, symbol=None, since=None, limit=None, params={}):
        """
        fetch the interest owed by the user for borrowing currency for margin trading
        see https://developer-pro.bitmart.com/en/spot/#get-borrow-record-isolated
        :param str|None code: unified currency code
        :param str symbol: unified market symbol when fetch interest in isolated markets
        :param int|None since: the earliest time in ms to fetch borrrow interest for
        :param int|None limit: the maximum number of structures to retrieve
        :param dict params: extra parameters specific to the bitmart api endpoint
        :returns [dict]: a list of `borrow interest structures <https://docs.ccxt.com/en/latest/manual.html#borrow-interest-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchBorrowInterest() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['N'] = limit
        if since is not None:
            request['start_time'] = since
        response = self.privateGetSpotV1MarginIsolatedBorrowRecord(self.extend(request, params))
        #
        #     {
        #         "message": "OK",
        #         "code": 1000,
        #         "trace": "8ea27a2a-4aba-49fa-961d-43a0137b0ef3",
        #         "data": {
        #             "records": [
        #                 {
        #                     "borrow_id": "1659045283903rNvJnuRTJNL5J53n",
        #                     "symbol": "BTC_USDT",
        #                     "currency": "USDT",
        #                     "borrow_amount": "100.00000000",
        #                     "daily_interest": "0.00055000",
        #                     "hourly_interest": "0.00002291",
        #                     "interest_amount": "0.00229166",
        #                     "create_time": 1659045284000
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        rows = self.safe_value(data, 'records', [])
        interest = self.parse_borrow_interests(rows, market)
        return self.filter_by_currency_since_limit(interest, code, since, limit)

    def parse_borrow_interest(self, info, market=None):
        #
        #     {
        #         "borrow_id": "1657664327844Lk5eJJugXmdHHZoe",
        #         "symbol": "BTC_USDT",
        #         "currency": "USDT",
        #         "borrow_amount": "20.00000000",
        #         "daily_interest": "0.00055000",
        #         "hourly_interest": "0.00002291",
        #         "interest_amount": "0.00045833",
        #         "create_time": 1657664329000
        #     }
        #
        marketId = self.safe_string(info, 'symbol')
        market = self.safe_market(marketId, market)
        timestamp = self.safe_integer(info, 'create_time')
        return {
            'symbol': self.safe_string(market, 'symbol'),
            'marginMode': 'isolated',
            'currency': self.safe_currency_code(self.safe_string(info, 'currency')),
            'interest': self.safe_number(info, 'interest_amount'),
            'interestRate': self.safe_number(info, 'hourly_interest'),
            'amountBorrowed': self.safe_number(info, 'borrow_amount'),
            'timestamp': timestamp,  # borrow creation time
            'datetime': self.iso8601(timestamp),
            'info': info,
        }

    def handle_margin_mode_and_params(self, methodName, params={}, defaultValue=None):
        """
         * @ignore
        marginMode specified by params["marginMode"], self.options["marginMode"], self.options["defaultMarginMode"], params["margin"] = True or self.options["defaultType"] = 'margin'
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [str|None, dict]: the marginMode in lowercase
        """
        marginMode = None
        marginMode, params = super(bitmart, self).handle_margin_mode_and_params(methodName, params, defaultValue)
        if marginMode is not None:
            if marginMode != 'isolated':
                raise NotSupported(self.id + ' only isolated margin is supported')
        return [marginMode, params]

    def nonce(self):
        return self.milliseconds()

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        baseUrl = self.implode_hostname(self.urls['api']['rest'])
        url = baseUrl + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        queryString = ''
        getOrDelete = (method == 'GET') or (method == 'DELETE')
        if getOrDelete:
            if query:
                queryString = self.urlencode(query)
                url += '?' + queryString
        if api == 'private':
            self.check_required_credentials()
            timestamp = str(self.milliseconds())
            headers = {
                'X-BM-KEY': self.apiKey,
                'X-BM-TIMESTAMP': timestamp,
                'Content-Type': 'application/json',
            }
            if not getOrDelete:
                body = self.json(query)
                queryString = body
            auth = timestamp + '#' + self.uid + '#' + queryString
            signature = self.hmac(self.encode(auth), self.encode(self.secret))
            headers['X-BM-SIGN'] = signature
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        #
        # spot
        #
        #     {"message":"Bad Request [to is empty]","code":50000,"trace":"f9d46e1b-4edb-4d07-a06e-4895fb2fc8fc","data":{}}
        #     {"message":"Bad Request [from is empty]","code":50000,"trace":"579986f7-c93a-4559-926b-06ba9fa79d76","data":{}}
        #     {"message":"Kline size over 500","code":50004,"trace":"d625caa8-e8ca-4bd2-b77c-958776965819","data":{}}
        #     {"message":"Balance not enough","code":50020,"trace":"7c709d6a-3292-462c-98c5-32362540aeef","data":{}}
        #
        # contract
        #
        #     {"errno":"OK","message":"INVALID_PARAMETER","code":49998,"trace":"eb5ebb54-23cd-4de2-9064-e090b6c3b2e3","data":null}
        #
        message = self.safe_string_lower(response, 'message')
        errorCode = self.safe_string(response, 'code')
        if ((errorCode is not None) and (errorCode != '1000')) or ((message is not None) and (message != 'ok')):
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], errorCode, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
