# -*- 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.abstract.bitmart import ImplicitAPI import hashlib from ccxt.base.types import Any, Balances, BorrowInterest, Currencies, Currency, DepositAddress, FundingHistory, Int, IsolatedBorrowRate, IsolatedBorrowRates, LedgerEntry, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, Trade, TradingFeeInterface, Transaction, MarketInterface, TransferEntry from typing import List 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 NetworkError from ccxt.base.errors import RateLimitExceeded from ccxt.base.errors import ExchangeNotAvailable from ccxt.base.errors import OnMaintenance from ccxt.base.errors import InvalidNonce from ccxt.base.decimal_to_precision import TRUNCATE from ccxt.base.decimal_to_precision import TICK_SIZE from ccxt.base.precise import Precise class bitmart(Exchange, ImplicitAPI): def describe(self) -> Any: 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': True, 'future': False, 'option': False, 'borrowCrossMargin': False, 'borrowIsolatedMargin': True, 'cancelAllOrders': True, 'cancelOrder': True, 'cancelOrders': True, 'createMarketBuyOrderWithCost': True, 'createMarketOrderWithCost': False, 'createMarketSellOrderWithCost': False, 'createOrder': True, 'createOrders': True, 'createPostOnlyOrder': True, 'createReduceOnlyOrder': True, 'createStopLimitOrder': False, 'createStopMarketOrder': False, 'createStopOrder': False, 'createTrailingPercentOrder': True, 'fetchBalance': True, 'fetchBorrowInterest': True, 'fetchBorrowRateHistories': False, 'fetchBorrowRateHistory': False, 'fetchCanceledOrders': True, 'fetchClosedOrders': True, 'fetchCrossBorrowRate': False, 'fetchCrossBorrowRates': False, 'fetchCurrencies': True, 'fetchDeposit': True, 'fetchDepositAddress': True, 'fetchDepositAddresses': False, 'fetchDepositAddressesByNetwork': False, 'fetchDeposits': True, 'fetchDepositWithdrawFee': True, 'fetchDepositWithdrawFees': False, 'fetchFundingHistory': True, 'fetchFundingRate': True, 'fetchFundingRateHistory': False, 'fetchFundingRates': False, 'fetchIsolatedBorrowRate': True, 'fetchIsolatedBorrowRates': True, 'fetchLedger': True, 'fetchLiquidations': False, 'fetchMarginMode': False, 'fetchMarkets': True, 'fetchMarkOHLCV': True, 'fetchMyLiquidations': True, 'fetchMyTrades': True, 'fetchOHLCV': True, 'fetchOpenInterest': True, 'fetchOpenInterestHistory': False, 'fetchOpenOrders': True, 'fetchOrder': True, 'fetchOrderBook': True, 'fetchOrders': False, 'fetchOrderTrades': True, 'fetchPosition': True, 'fetchPositionMode': True, 'fetchPositions': True, 'fetchStatus': True, 'fetchTicker': True, 'fetchTickers': True, 'fetchTime': True, 'fetchTrades': True, 'fetchTradingFee': True, 'fetchTradingFees': False, 'fetchTransactionFee': True, 'fetchTransactionFees': False, 'fetchTransfer': False, 'fetchTransfers': True, 'fetchWithdrawAddresses': True, 'fetchWithdrawAddressesByNetwork': False, 'fetchWithdrawal': True, 'fetchWithdrawals': True, 'reduceMargin': False, 'repayCrossMargin': False, 'repayIsolatedMargin': True, 'setLeverage': True, 'setMarginMode': False, 'setPositionMode': True, 'transfer': True, 'withdraw': True, }, 'hostname': 'bitmart.com', # bitmart.info, bitmart.news for Hong Kong users 'urls': { 'logo': 'https://github.com/user-attachments/assets/0623e9c4-f50e-48c9-82bd-65c3908c3a14', 'api': { 'spot': 'https://api-cloud.{hostname}', 'swap': 'https://api-cloud-v2.{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, # 10 times/sec => 30/10 = 3 'system/service': 3, # spot markets 'spot/v1/currencies': 7.5, 'spot/v1/symbols': 7.5, 'spot/v1/symbols/details': 5, 'spot/quotation/v3/tickers': 6, # 10 times/2 sec = 5/s => 30/5 = 6 'spot/quotation/v3/ticker': 4, # 15 times/2 sec = 7.5/s => 30/7.5 = 4 'spot/quotation/v3/lite-klines': 5, # should be 4 but errors 'spot/quotation/v3/klines': 7, # should be 6 but errors 'spot/quotation/v3/books': 4, # 15 times/2 sec = 7.5/s => 30/7.5 = 4 'spot/quotation/v3/trades': 4, # 15 times/2 sec = 7.5/s => 30/7.5 = 4 'spot/v1/ticker': 5, 'spot/v2/ticker': 30, 'spot/v1/ticker_detail': 5, # 12 times/2 sec = 6/s => 30/6 = 5 'spot/v1/steps': 30, 'spot/v1/symbols/kline': 6, # should be 5 but errors '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/funding-rate-history': 30, 'contract/public/kline': 6, # should be 5 but errors 'account/v1/currencies': 30, 'contract/public/markprice-kline': 5, # 6 times per 1 second }, }, '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/contract/sub-account/main/v1/wallet': 5, 'account/contract/sub-account/main/v1/transfer-list': 7.5, 'account/contract/sub-account/v1/transfer-history': 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, 'account/v1/withdraw/address/list': 30, # 2 times per 2 seconds # order 'spot/v1/order_detail': 1, 'spot/v2/orders': 5, 'spot/v1/trades': 5, # newer order endpoint 'spot/v2/trades': 4, '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': 30, 'spot/v1/margin/isolated/account': 5, 'spot/v1/trade_fee': 30, 'spot/v1/user_fee': 30, # broker 'spot/v1/broker/rebate': 1, # contract 'contract/private/assets-detail': 5, 'contract/private/order': 1.2, 'contract/private/order-history': 10, 'contract/private/position': 10, 'contract/private/position-v2': 10, 'contract/private/get-open-orders': 1.2, 'contract/private/current-plan-order': 1.2, 'contract/private/trades': 10, 'contract/private/position-risk': 10, 'contract/private/affilate/rebate-list': 10, 'contract/private/affilate/trade-list': 10, 'contract/private/transaction-history': 10, 'contract/private/get-position-mode': 1, }, '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/contract/sub-account/main/v1/sub-to-main': 7.5, 'account/contract/sub-account/main/v1/main-to-sub': 7.5, 'account/contract/sub-account/sub/v1/sub-to-main': 7.5, # 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, 'spot/v4/query/order': 1, # 60 times/2 sec = 30/s => 30/30 = 1 'spot/v4/query/client-order': 1, # 60 times/2 sec = 30/s => 30/30 = 1 'spot/v4/query/open-orders': 5, # 12 times/2 sec = 6/s => 30/6 = 5 'spot/v4/query/history-orders': 5, # 12 times/2 sec = 6/s => 30/6 = 5 'spot/v4/query/trades': 5, # 12 times/2 sec = 6/s => 30/6 = 5 'spot/v4/query/order-trades': 5, # 12 times/2 sec = 6/s => 30/6 = 5 'spot/v4/cancel_orders': 3, 'spot/v4/cancel_all': 90, 'spot/v4/batch_orders': 3, # newer endpoint 'spot/v3/cancel_order': 1, 'spot/v2/batch_orders': 1, 'spot/v2/submit_order': 1, # margin 'spot/v1/margin/submit_order': 1.5, # 20 times per second 'spot/v1/margin/isolated/borrow': 30, 'spot/v1/margin/isolated/repay': 30, 'spot/v1/margin/isolated/transfer': 30, # contract 'account/v1/transfer-contract-list': 60, 'account/v1/transfer-contract': 60, 'contract/private/submit-order': 2.5, 'contract/private/cancel-order': 1.5, 'contract/private/cancel-orders': 30, 'contract/private/submit-plan-order': 2.5, 'contract/private/cancel-plan-order': 1.5, 'contract/private/submit-leverage': 2.5, 'contract/private/submit-tp-sl-order': 2.5, 'contract/private/modify-plan-order': 2.5, 'contract/private/modify-preset-plan-order': 2.5, 'contract/private/modify-limit-order': 2.5, 'contract/private/modify-tp-sl-order': 2.5, 'contract/private/submit-trail-order': 2.5, # weight is not provided by the exchange, is set order 'contract/private/cancel-trail-order': 1.5, # weight is not provided by the exchange, is set order 'contract/private/set-position-mode': 1, }, }, }, '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.0040'), 'maker': self.parse_number('0.0035'), '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 '30016': OnMaintenance, # 200, Service maintenance, the function is temporarily unavailable '30017': RateLimitExceeded, # 418, Your account request is temporarily rejected due to violation of current limiting rules '30018': BadRequest, # 503, Request Body requires JSON format '30019': PermissionDenied, # 200, You do not have the permissions to perform self operation # funding account & sub 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 hasattr(self, supported) 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 '60026': PermissionDenied, # 403, Sub-account does not have permission to operate '60027': PermissionDenied, # 403, Only supports sub-account calls '60028': AccountSuspended, # 403, Account is disabled for security reasons, please contact customer service '60029': AccountSuspended, # 403, The account is frozen by the master account, please contact the master account to unfreeze the account '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, # 400, {"message":"sub-account not found","code":61003,"trace":"b35ec2fd-0bc9-4ef2-a3c0-6f78d4f335a4","data":{}} '61004': BadRequest, # 400, Duplicate requests(such an existing requestNo) '61005': BadRequest, # 403, Asset transfer between accounts is not available '61006': NotSupported, # 403, The sub-account api only supports organization accounts '61007': ExchangeError, # 403, Please complete your institution verification to enable withdrawal function. '61008': ExchangeError, # 403, Suspend transfer out # spot public errors '70000': ExchangeError, # 200, no data '70001': BadRequest, # 200, request param can not be null '70002': BadSymbol, # 200, symbol is invalid '70003': NetworkError, # {"code":70003,"trace":"81a9d57b63be4819b65d3065e6a4682b.105.17105295323593915","message":"net error, please try later","data":null} '71001': BadRequest, # 200, after is invalid '71002': BadRequest, # 200, before is invalid '71003': BadRequest, # 200, request after or before is invalid '71004': BadRequest, # 200, request kline count limit '71005': BadRequest, # 200, request step error # spot & margin 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': 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 '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 '50024': BadRequest, # 400, Order book size over 200 '50025': BadRequest, # 400, Maximum price is %s '50026': BadRequest, # 400, The buy order price cannot be higher than the open price '50027': BadRequest, # 400, The sell order price cannot be lower than the open price '50028': BadRequest, # 400, Missing parameters '50029': InvalidOrder, # 400, {"message":"param not match : size * price >=1000","code":50029,"trace":"f931f030-b692-401b-a0c5-65edbeadc598","data":{}} '50030': OrderNotFound, # 400, {"message":"Order is already canceled","code":50030,"trace":"8d6f64ee-ad26-45a4-9efd-1080f9fca1fa","data":{}} '50031': OrderNotFound, # 400, Order is already completed '50032': OrderNotFound, # 400, {"message":"Order does not exist","code":50032,"trace":"8d6b482d-4bf2-4e6c-aab2-9dcd22bf2481","data":{}} '50033': InvalidOrder, # 400, The order quantity should be greater than 0 and less than or equal to 10 # below Error codes used interchangeably for both failed postOnly and IOC orders depending on market price and order side '50034': InvalidOrder, # 400, {"message":"The price is high and there is no matching depth","code":50034,"trace":"ebfae59a-ba69-4735-86b2-0ed7b9ca14ea","data":{}} '50035': InvalidOrder, # 400, {"message":"The price is low and there is no matching depth","code":50035,"trace":"677f01c7-8b88-4346-b097-b4226c75c90e","data":{}} '50036': ExchangeError, # 400, Cancel failed, order is not revocable status '50037': BadRequest, # 400, The maximum length of clientOrderId cannot exceed 32 '50038': BadRequest, # 400, ClientOrderId only allows a combination of numbers and letters '50039': BadRequest, # 400, Order_id and clientOrderId cannot be empty at the same time '50040': BadSymbol, # 400, Symbol Not Available '50041': ExchangeError, # 400, Out of query time range '50042': BadRequest, # 400, clientOrderId is duplicate '51000': BadSymbol, # 400, Currency not found '51001': ExchangeError, # 400, Margin Account not Opened '51002': ExchangeError, # 400, Margin Account Not Available '51003': ExchangeError, # 400, Account Limit '51004': InsufficientFunds, # 400, {"message":"Exceed the maximum number of borrows available.","code":51004,"trace":"4030b753-9beb-44e6-8352-1633c5edcd47","data":{}} '51005': InvalidOrder, # 400, Less than the minimum borrowable amount '51006': InvalidOrder, # 400, Exceeds the amount to be repaid '51007': BadRequest, # 400, order_mode not found '51008': ExchangeError, # 400, Operation is limited, please try again later '51009': InvalidOrder, # 400, Parameter mismatch: limit order/market order quantity should be greater than the minimum number of should buy/sell '51010': InvalidOrder, # 400, Parameter mismatch: limit order price should be greater than the minimum buy price '51011': InvalidOrder, # 400, {"message":"param not match : size * price >=5","code":51011,"trace":"525e1d27bfd34d60b2d90ba13a7c0aa9.74.16696421352220797","data":{}} '51012': InvalidOrder, # 400, Parameter mismatch: limit order price should be greater than the minimum buy price '51013': InvalidOrder, # 400, Parameter mismatch: Limit order quantity * price should be greater than the minimum transaction amount '51014': InvalidOrder, # 400, Participation mismatch: the number of market order buy orders should be greater than the minimum buyable amount '51015': InvalidOrder, # 400, Parameter mismatch: the price of market order buy order placed is too small '52000': BadRequest, # 400, Unsupported OrderMode Type '52001': BadRequest, # 400, Unsupported Trade Type '52002': BadRequest, # 400, Unsupported Side Type '52003': BadRequest, # 400, Unsupported Query State Type '52004': BadRequest, # 400, End time must be greater than or equal to Start time '53000': AccountSuspended, # 403, Your account is frozen due to security policies. Please contact customer service '53001': AccountSuspended, # 403, {"message":"Your kyc country is restricted. Please contact customer service.","code":53001,"trace":"8b445940-c123-4de9-86d7-73c5be2e7a24","data":{}} '53002': PermissionDenied, # 403, Your account has not yet completed the kyc advanced certification, please complete first '53003': PermissionDenied, # 403 No permission, please contact the main account '53005': PermissionDenied, # 403 Don't have permission to access the interface '53006': PermissionDenied, # 403 Please complete your personal verification(Starter) '53007': PermissionDenied, # 403 Please complete your personal verification(Advanced) '53008': PermissionDenied, # 403 Services is not available in your countries and areas '53009': PermissionDenied, # 403 Your account has not yet completed the qr code certification, please complete first '53010': PermissionDenied, # 403 This account is restricted from borrowing '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 '59003': ExchangeError, # 500, Spot wallet call fail '59004': ExchangeError, # 500, Margin wallet service call exception '59005': PermissionDenied, # 500, Margin wallet service restricted '59006': ExchangeError, # 500, Transfer fail '59007': ExchangeError, # 500, Get symbol risk data fail '59008': ExchangeError, # 500, Trading order failure '59009': ExchangeError, # 500, Loan success,but trading order failure '59010': InsufficientFunds, # 500, Insufficient loan amount. '59011': ExchangeError, # 500, The Get Wallet Balance service call fail, please try again later # 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 '40035': OrderNotFound, # 400, The order is not exist '40036': InvalidOrder, # 400, The order status is invalid '40037': OrderNotFound, # 400, The order id is not exist '40038': BadRequest, # 400, The k-line step is invalid '40039': BadRequest, # 400, The timestamp is invalid '40040': InvalidOrder, # 400, The order leverage is invalid '40041': InvalidOrder, # 400, The order side is invalid '40042': InvalidOrder, # 400, The order type is invalid '40043': InvalidOrder, # 400, The order precision is invalid '40044': InvalidOrder, # 400, The order range is invalid '40045': InvalidOrder, # 400, The order open type is invalid '40046': PermissionDenied, # 403, The account is not opened futures '40047': PermissionDenied, # 403, Services is not available in you countries and areas '40048': InvalidOrder, # 403, ClientOrderId only allows a combination of numbers and letters '40049': InvalidOrder, # 403, The maximum length of clientOrderId cannot exceed 32 '40050': InvalidOrder, # 403, Client OrderId duplicated with existing orders }, 'broad': { 'You contract account available balance not enough': InsufficientFunds, 'you contract account available balance not enough': InsufficientFunds, }, }, 'commonCurrencies': { '$GM': 'GOLDMINER', '$HERO': 'Step Hero', '$PAC': 'PAC', 'BP': 'BEYOND', 'GDT': 'Gorilla Diamond', 'GLD': 'Goldario', 'MVP': 'MVP Coin', 'TRU': 'Truebit', # conflict with TrueFi }, 'options': { 'defaultNetworks': { 'USDT': 'TRC20', 'BTC': 'BTC', 'ETH': 'ERC20', }, 'timeDifference': 0, # the difference between system clock and exchange clock 'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation 'networks': { 'ERC20': 'ERC20', 'SOL': 'SOL', 'BTC': 'BTC', 'TRC20': 'TRC20', # todo: should be TRX after unification # 'TRC20': ['TRC20', 'trc20', 'TRON'], # todo: after unification i.e. TRON is returned from fetchDepositAddress # 'ERC20': ['ERC20', 'ERC-20', 'ERC20 '], # todo: after unification 'OMNI': 'OMNI', 'XLM': 'XLM', 'EOS': 'EOS', 'NEO': 'NEO', 'BTM': 'BTM', 'BCH': 'BCH', 'LTC': 'LTC', 'BSV': 'BSV', 'XRP': 'XRP', # 'VECHAIN': ['VET', 'Vechain'], # todo: after unification 'PLEX': 'PLEX', 'XCH': 'XCH', # 'AVALANCHE_C': ['AVAX', 'AVAX-C'], # todo: after unification 'NEAR': 'NEAR', 'FIO': 'FIO', 'SCRT': 'SCRT', 'IOTX': 'IOTX', 'ALGO': 'ALGO', 'ATOM': 'ATOM', 'DOT': 'DOT', 'ADA': 'ADA', 'DOGE': 'DOGE', 'XYM': 'XYM', 'GLMR': 'GLMR', 'MOVR': 'MOVR', 'ZIL': 'ZIL', 'INJ': 'INJ', 'KSM': 'KSM', 'ZEC': 'ZEC', 'NAS': 'NAS', 'POLYGON': 'MATIC', 'HRC20': 'HECO', 'XDC': 'XDC', 'ONE': 'ONE', 'LAT': 'LAT', 'CSPR': 'Casper', 'ICP': 'Computer', 'XTZ': 'XTZ', 'MINA': 'MINA', 'BEP20': 'BSC_BNB', 'THETA': 'THETA', 'AKT': 'AKT', 'AR': 'AR', 'CELO': 'CELO', 'FIL': 'FIL', 'NULS': 'NULS', 'ETC': 'ETC', 'DASH': 'DASH', 'DGB': 'DGB', 'BEP2': 'BEP2', 'GRIN': 'GRIN', 'WAVES': 'WAVES', 'ABBC': 'ABBC', 'ACA': 'ACA', 'QTUM': 'QTUM', 'PAC': 'PAC', # 'TERRACLASSIC': 'LUNC', # TBD # 'TERRA': 'Terra', # TBD # 'HEDERA': ['HBAR', 'Hedera', 'Hedera Mainnet'], # todo: after unification 'TLOS': 'TLOS', 'KARDIA': 'KardiaChain', 'FUSE': 'FUSE', 'TRC10': 'TRC10', 'FIRO': 'FIRO', 'FTM': 'Fantom', # 'KLAYTN': ['klaytn', 'KLAY', 'Klaytn'], # todo: after unification # 'ELROND': ['EGLD', 'Elrond eGold', 'MultiversX'], # todo: after unification 'EVER': 'EVER', 'KAVA': 'KAVA', 'HYDRA': 'HYDRA', 'PLCU': 'PLCU', 'BRISE': 'BRISE', # 'CRC20': ['CRO', 'CRO_Chain'], # todo: after unification # 'CONFLUX': ['CFX eSpace', 'CFX'], # todo: after unification 'OPTIMISM': 'OPTIMISM', 'REEF': 'REEF', 'SYS': 'SYS', # NEVM is different 'VITE': 'VITE', 'STX': 'STX', 'SXP': 'SXP', 'BITCI': 'BITCI', # 'ARBITRUM': ['ARBI', 'Arbitrum'], # todo: after unification 'XRD': 'XRD', 'ASTR': 'ASTAR', 'ZEN': 'HORIZEN', 'LTO': 'LTO', 'ETHW': 'ETHW', 'ETHF': 'ETHF', 'IOST': 'IOST', # 'CHILIZ': ['CHZ', 'CHILIZ'], # todo: after unification 'APT': 'APT', # 'FLOW': ['FLOW', 'Flow'], # todo: after unification 'ONT': 'ONT', 'EVMOS': 'EVMOS', 'XMR': 'XMR', 'OASYS': 'OAS', 'OSMO': 'OSMO', 'OMAX': 'OMAX Chain', 'DESO': 'DESO', 'BFIC': 'BFIC', 'OHO': 'OHO', 'CS': 'CS', 'CHEQ': 'CHEQ', 'NODL': 'NODL', 'NEM': 'XEM', 'FRA': 'FRA', 'ERGO': 'ERG', # todo: below will be uncommented after unification # 'BITCOINHD': 'BHD', # 'CRUST': 'CRU', # 'MINTME': 'MINTME', # 'ZENITH': 'ZENITH', # 'ZENIQ': 'ZENIQ', # "ZEN-20" is different # 'BITCOINVAULT': 'BTCV', # 'MOBILECOIN': 'MBX', # 'PINETWORK': 'PI', # 'PI': 'PI', # 'REBUS': 'REBUS', # 'XODEX': 'XODEX', # 'ULTRONGLOW': 'UTG' # 'QIBLOCKCHAIN': 'QIE', # 'XIDEN': 'XDEN', # 'PHAETON': 'PHAE', # 'REDLIGHT': 'REDLC', # 'VERITISE': 'VTS', # 'VERIBLOCK': 'VBK', # 'RAMESTTA': 'RAMA', # 'BITICA': 'BDCC', # 'CROWNSOVEREIGN': 'CSOV', # 'DRAC': 'DRC20', # 'QCHAIN': 'QDT', # 'KINGARU': 'KRU', # 'PROOFOFMEMES': 'POM', # 'CUBE': 'CUBE', # 'CADUCEUS': 'CMP', # 'VEIL': 'VEIL', # 'ENERGYWEB': 'EWT', # 'CYPHERIUM': 'CPH', # 'LBRY': 'LBC', # 'ETHERCOIN': 'ETE', # undetermined chains: # LEX(for LexThum), TAYCAN(for TRICE), SFL(probably TAYCAN), OMNIA(for APEX), NAC(for NAC), KAG(Kinesis), CEM(crypto emergency), XVM(for Venidium), NEVM(for NEVM), IGT20(for IGNITE), FILM(FILMCredits), CC(CloudCoin), MERGE(MERGE), LTNM(Bitcoin latinum), PLUGCN( PlugChain), DINGO(dingo), LED(LEDGIS), AVAT(AVAT), VSOL(Vsolidus), EPIC(EPIC cash), NFC(netflowcoin), mrx(Metrix Coin), Idena(idena network), PKT(PKT Cash), BondDex(BondDex), XBN(XBN), KALAM(Kalamint), REV(RChain), KRC20(MyDeFiPet), ARC20(Hurricane Token), GMD(Coop network), BERS(Berith), ZEBI(Zebi), BRC(Baer Chain), DAPS(DAPS Coin), APL(Gold Secured Currency), NDAU(NDAU), WICC(WICC), UPG(Unipay God), TSL(TreasureSL), MXW(Maxonrow), CLC(Cifculation), SMH(SMH Coin), XIN(CPCoin), RDD(ReddCoin), OK(Okcash), KAR(KAR), CCX(ConcealNetwork), }, 'networksById': { 'ETH': 'ERC20', 'Ethereum': 'ERC20', 'USDT': 'OMNI', # the default USDT network for bitmart is OMNI 'Bitcoin': 'BTC', }, 'defaultType': 'spot', # 'spot', 'swap' 'fetchBalance': { 'type': 'spot', # 'spot', 'swap', 'account' }, 'accountsByType': { 'spot': 'spot', 'swap': 'swap', }, 'createMarketBuyOrderRequiresPrice': True, 'brokerId': 'CCXTxBitmart000', }, 'features': { 'default': { 'sandbox': False, 'createOrder': { 'marginMode': True, 'triggerPrice': False, 'triggerPriceType': None, 'triggerDirection': False, 'stopLossPrice': False, 'takeProfitPrice': False, 'attachedStopLossTakeProfit': None, 'timeInForce': { 'IOC': True, 'FOK': False, 'PO': True, 'GTD': False, }, 'hedged': False, 'trailing': False, 'marketBuyRequiresPrice': False, # todo: https://developer-pro.bitmart.com/en/spot/#new-order-v2-signed 'marketBuyByCost': True, 'leverage': True, # todo: implement 'selfTradePrevention': False, 'iceberg': False, }, 'createOrders': { 'max': 10, }, 'fetchMyTrades': { 'marginMode': True, 'limit': 200, 'daysBack': None, 'untilDays': 99999, 'symbolRequired': False, }, 'fetchOrder': { 'marginMode': False, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOpenOrders': { 'marginMode': True, 'limit': 200, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOrders': None, 'fetchClosedOrders': { 'marginMode': True, 'limit': 200, 'daysBack': None, 'daysBackCanceled': None, 'untilDays': None, 'trigger': False, 'trailing': False, 'symbolRequired': False, }, 'fetchOHLCV': { 'limit': 1000, # variable timespans for recent endpoint, 200 for historical }, }, 'forDerivatives': { 'extends': 'default', 'createOrder': { 'marginMode': True, 'triggerPrice': True, 'triggerPriceType': { 'last': True, 'mark': True, 'index': False, }, 'triggerDirection': True, # todo: implementation broken 'stopLossPrice': True, 'takeProfitPrice': True, 'attachedStopLossTakeProfit': { 'triggerPriceType': { 'last': True, 'mark': True, 'index': False, }, 'price': False, }, 'timeInForce': { 'IOC': True, 'FOK': True, 'PO': True, 'GTD': False, }, 'hedged': False, 'trailing': True, 'marketBuyRequiresPrice': True, 'marketBuyByCost': True, # exchange-supported features # 'selfTradePrevention': True, # 'twap': False, # 'iceberg': False, # 'oco': False, }, 'fetchMyTrades': { 'marginMode': True, 'limit': None, 'daysBack': None, 'untilDays': 99999, }, 'fetchOrder': { 'marginMode': False, 'trigger': False, 'trailing': True, }, 'fetchOpenOrders': { 'marginMode': False, 'limit': 100, 'trigger': True, 'trailing': False, }, 'fetchClosedOrders': { 'marginMode': True, 'limit': 200, 'daysBack': None, 'daysBackCanceled': None, 'untilDays': None, 'trigger': False, 'trailing': False, }, 'fetchOHLCV': { 'limit': 500, }, }, 'spot': { 'extends': 'default', }, 'swap': { 'linear': { 'extends': 'forDerivatives', }, 'inverse': { 'extends': 'forDerivatives', }, }, 'future': { 'linear': None, 'inverse': None, }, }, }) def fetch_time(self, params={}) -> Int: """ fetches the current integer timestamp in milliseconds from the exchange server https://developer-pro.bitmart.com/en/spot/#get-system-time :param dict [params]: extra parameters specific to the exchange API endpoint :returns int: the current integer timestamp in milliseconds from the exchange server """ response = self.publicGetSystemTime(params) # # { # "message":"OK", # "code":1000, # "trace":"c4e5e5b7-fe9f-4191-89f7-53f6c5bf9030", # "data":{ # "server_time":1599843709578 # } # } # data = self.safe_dict(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 https://developer-pro.bitmart.com/en/spot/#get-system-service-status :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `status structure ` """ options = self.safe_dict(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_dict(response, 'data', {}) services = self.safe_list(data, 'service', []) servicesByType = self.index_by(services, 'service_type') if type == 'swap': type = 'contract' service = self.safe_string(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={}) -> List[MarketInterface]: 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_dict(response, 'data', {}) symbols = self.safe_list(data, 'symbols', []) result = [] fees = self.fees['trading'] 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(self.safe_market_structure({ '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': self.safe_string_lower_2(market, 'status', 'trade_status') == 'trading', 'contract': False, 'linear': None, 'inverse': None, 'contractSize': None, 'expiry': None, 'expiryDatetime': None, 'strike': None, 'optionType': None, 'maker': fees['maker'], 'taker': fees['taker'], '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, }, }, 'created': None, 'info': market, })) return result def fetch_contract_markets(self, params={}) -> List[MarketInterface]: response = self.publicGetContractPublicDetails(params) # # { # "code": 1000, # "message": "Ok", # "data": { # "symbols": [ # { # "symbol": "BTCUSDT", # "product_type": 1, # "open_timestamp": 1645977600000, # "expire_timestamp": 0, # "settle_timestamp": 0, # "base_currency": "BTC", # "quote_currency": "USDT", # "last_price": "63547.4", # "volume_24h": "110938430", # "turnover_24h": "7004836342.6944", # "index_price": "63587.85404255", # "index_name": "BTCUSDT", # "contract_size": "0.001", # "min_leverage": "1", # "max_leverage": "100", # "price_precision": "0.1", # "vol_precision": "1", # "max_volume": "1000000", # "min_volume": "1", # "funding_rate": "0.0000801", # "expected_funding_rate": "-0.0000035", # "open_interest": "278214", # "open_interest_value": "17555316.9355496", # "high_24h": "64109.4", # "low_24h": "61857.6", # "change_24h": "0.0239264900886327", # "funding_time": 1726819200000 # }, # ] # } # } # data = self.safe_dict(response, 'data', {}) symbols = self.safe_list(data, 'symbols', []) result = [] fees = self.fees['trading'] for i in range(0, len(symbols)): market = symbols[i] id = self.safe_string(market, 'symbol') 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) settleId = 'USDT' # self is bitmart's ID for usdt settle = self.safe_currency_code(settleId) symbol = base + '/' + quote + ':' + settle productType = self.safe_integer(market, 'product_type') isSwap = (productType == 1) isFutures = (productType == 2) expiry = self.safe_integer(market, 'expire_timestamp') if not isFutures and (expiry == 0): expiry = None result.append(self.safe_market_structure({ 'id': id, 'numericId': None, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': 'swap' if isSwap else 'future', 'spot': False, 'margin': False, 'swap': isSwap, 'future': isFutures, 'option': False, 'active': self.safe_string_lower(market, 'status') == 'trading', 'contract': True, 'linear': True, 'inverse': False, 'contractSize': self.safe_number(market, 'contract_size'), 'expiry': expiry, 'expiryDatetime': self.iso8601(expiry), 'strike': None, 'optionType': None, 'maker': fees['maker'], 'taker': fees['taker'], 'precision': { 'amount': self.safe_number(market, 'vol_precision'), 'price': self.safe_number(market, 'price_precision'), }, 'limits': { 'leverage': { 'min': self.safe_number(market, 'min_leverage'), 'max': self.safe_number(market, 'max_leverage'), }, 'amount': { 'min': self.safe_number(market, 'min_volume'), 'max': self.safe_number(market, 'max_volume'), }, 'price': { 'min': None, 'max': None, }, 'cost': { 'min': None, 'max': None, }, }, 'created': self.safe_integer(market, 'open_timestamp'), 'info': market, })) return result def fetch_markets(self, params={}) -> List[Market]: """ retrieves data on all markets for bitmart https://developer-pro.bitmart.com/en/spot/#get-trading-pair-details-v1 https://developer-pro.bitmart.com/en/futuresv2/#get-contract-details :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: an array of objects representing market data """ if self.options['adjustForTimeDifference']: self.load_time_difference() spot = self.fetch_spot_markets(params) contract = self.fetch_contract_markets(params) return self.array_concat(spot, contract) def fetch_currencies(self, params={}) -> Currencies: """ fetches all available currencies on an exchange https://developer-pro.bitmart.com/en/spot/#get-currency-list-v1 :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an associative dictionary of currencies """ response = self.publicGetAccountV1Currencies(params) # # { # "message": "OK", # "code": 1000, # "trace": "619294ecef584282b26a3be322b1e01f.66.17403093228242229", # "data": { # "currencies": [ # { # "currency": "BTC", # "name": "Bitcoin", # "recharge_minsize": '0.00000001', # "contract_address": null, # "network": "BTC", # "withdraw_enabled": True, # "deposit_enabled": True, # "withdraw_minsize": "0.0003", # "withdraw_minfee": "9.61", # "withdraw_fee_estimate": "9.61", # "withdraw_fee": "0.0001" # } # ] # } # } # data = self.safe_dict(response, 'data', {}) currencies = self.safe_list(data, 'currencies', []) result = {} for i in range(0, len(currencies)): currency = currencies[i] fullId = self.safe_string(currency, 'currency') currencyId = fullId networkId = self.safe_string(currency, 'network') isNtf = (fullId.find('NFT') >= 0) if not isNtf: parts = fullId.split('-') currencyId = self.safe_string(parts, 0) second = self.safe_string(parts, 1) if second is not None: networkId = second.upper() currencyCode = self.safe_currency_code(currencyId) entry = self.safe_dict(result, currencyCode) if entry is None: entry = { 'info': currency, 'id': currencyId, 'code': currencyCode, 'precision': None, 'name': self.safe_string(currency, 'name'), 'deposit': None, 'withdraw': None, 'active': None, 'networks': {}, 'type': 'other' if isNtf else 'crypto', } networkCode = self.network_id_to_code(networkId) withdraw = self.safe_bool(currency, 'withdraw_enabled') deposit = self.safe_bool(currency, 'deposit_enabled') entry['networks'][networkCode] = { 'info': currency, 'id': networkId, 'code': networkCode, 'withdraw': withdraw, 'deposit': deposit, 'active': withdraw and deposit, 'fee': self.safe_number(currency, 'withdraw_fee'), 'limits': { 'withdraw': { 'min': self.safe_number(currency, 'withdraw_minsize'), 'max': None, }, 'deposit': { 'min': None, 'max': None, }, }, } result[currencyCode] = entry keys = list(result.keys()) for i in range(0, len(keys)): key = keys[i] currency = result[key] result[key] = self.safe_currency_structure(currency) return result def get_currency_id_from_code_and_network(self, currencyCode: Str, networkCode: Str) -> Str: if networkCode is None: networkCode = self.default_network_code(currencyCode) # use default network code if not provided currency = self.currency(currencyCode) id = currency['id'] idFromNetwork: Str = None networks = self.safe_dict(currency, 'networks', {}) networkInfo: dict = {} if networkCode is None: # network code is not provided and there is no default network code network = self.safe_dict(networks, currencyCode) # trying to find network that has the same code if network is None: # use the first network in the networks list if there is no network code with the same code keys = list(networks.keys()) length = len(keys) if length > 0: network = self.safe_value(networks, keys[0]) networkInfo = self.safe_dict(network, 'info', {}) idFromNetwork = self.safe_string(networkInfo, 'currency') # use currency name from network else: providedOrDefaultNetwork = self.safe_dict(networks, networkCode) if providedOrDefaultNetwork is not None: networkInfo = self.safe_dict(providedOrDefaultNetwork, 'info', {}) idFromNetwork = self.safe_string(networkInfo, 'currency') # use currency name from network else: id += '-' + self.network_code_to_id(networkCode, currencyCode) # use concatenated currency id and network code if network is not found return idFromNetwork if (idFromNetwork is not None) else id def fetch_transaction_fee(self, code: str, params={}): """ @deprecated please use fetchDepositWithdrawFee instead :param str code: unified currency code :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.network]: the network code of the currency :returns dict: a `fee structure ` """ self.load_markets() currency = self.currency(code) network: Str = None network, params = self.handle_network_code_and_params(params) request: dict = { 'currency': self.get_currency_id_from_code_and_network(currency['code'], network), } 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: dict = {} withdrawFees[code] = self.safe_number(data, 'withdraw_fee') return { 'info': response, 'withdraw': withdrawFees, 'deposit': {}, } def parse_deposit_withdraw_fee(self, fee, currency: 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: str, params={}): """ fetch the fee for deposits and withdrawals https://developer-pro.bitmart.com/en/spot/#withdraw-quota-keyed :param str code: unified currency code :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.network]: the network code of the currency :returns dict: a `fee structure ` """ self.load_markets() network: Str = None network, params = self.handle_network_code_and_params(params) request: dict = { 'currency': self.get_currency_id_from_code_and_network(code, network), } 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: dict, market: Market = None) -> Ticker: # # spot(REST) fetchTickers # # { # 'result': [ # "AFIN_USDT", # symbol # "0.001047", # last # "11110", # v_24h # "11.632170", # qv_24h # "0.001048", # open_24h # "0.001048", # high_24h # "0.001047", # low_24h # "-0.00095", # price_change_24h # "0.001029", # bid_px # "5555", # bid_sz # "0.001041", # ask_px # "5297", # ask_sz # "1717122550482" # timestamp # ] # } # # spot(REST) fetchTicker # # { # "symbol": "BTC_USDT", # "last": "68500.00", # "v_24h": "10491.65490", # "qv_24h": "717178990.42", # "open_24h": "68149.75", # "high_24h": "69499.99", # "low_24h": "67132.40", # "fluctuation": "0.00514", # "bid_px": "68500", # "bid_sz": "0.00162", # "ask_px": "68500.01", # "ask_sz": "0.01722", # "ts": "1717131391671" # } # # spot(WS) # # { # "symbol":"BTC_USDT", # "last_price":"146.24", # "open_24h":"147.17", # "high_24h":"147.48", # "low_24h":"143.88", # "base_volume_24h":"117387.58", # NOT base, but quote currencynot !! # "s_t": 1610936002 # } # # swap # # { # "symbol": "BTCUSDT", # "product_type": 1, # "open_timestamp": 1645977600000, # "expire_timestamp": 0, # "settle_timestamp": 0, # "base_currency": "BTC", # "quote_currency": "USDT", # "last_price": "63547.4", # "volume_24h": "110938430", # "turnover_24h": "7004836342.6944", # "index_price": "63587.85404255", # "index_name": "BTCUSDT", # "contract_size": "0.001", # "min_leverage": "1", # "max_leverage": "100", # "price_precision": "0.1", # "vol_precision": "1", # "max_volume": "1000000", # "min_volume": "1", # "funding_rate": "0.0000801", # "expected_funding_rate": "-0.0000035", # "open_interest": "278214", # "open_interest_value": "17555316.9355496", # "high_24h": "64109.4", # "low_24h": "61857.6", # "change_24h": "0.0239264900886327", # "funding_time": 1726819200000 # } # result = self.safe_list(ticker, 'result', []) average = self.safe_string_2(ticker, 'avg_price', 'index_price') marketId = self.safe_string_2(ticker, 'symbol', 'contract_symbol') timestamp = self.safe_integer_2(ticker, 'timestamp', 'ts') last = self.safe_string_2(ticker, 'last_price', 'last') percentage = self.safe_string_2(ticker, 'price_change_percent_24h', 'change_24h') change = self.safe_string(ticker, 'fluctuation') high = self.safe_string_2(ticker, 'high_24h', 'high_price') low = self.safe_string_2(ticker, 'low_24h', 'low_price') bid = self.safe_string_2(ticker, 'best_bid', 'bid_px') bidVolume = self.safe_string_2(ticker, 'best_bid_size', 'bid_sz') ask = self.safe_string_2(ticker, 'best_ask', 'ask_px') askVolume = self.safe_string_2(ticker, 'best_ask_size', 'ask_sz') open = self.safe_string(ticker, 'open_24h') baseVolume = self.safe_string_n(ticker, ['base_volume_24h', 'v_24h', 'volume_24h']) quoteVolume = self.safe_string_lower_n(ticker, ['quote_volume_24h', 'qv_24h', 'turnover_24h']) listMarketId = self.safe_string(result, 0) if listMarketId is not None: marketId = listMarketId timestamp = self.safe_integer(result, 12) high = self.safe_string(result, 5) low = self.safe_string(result, 6) bid = self.safe_string(result, 8) bidVolume = self.safe_string(result, 9) ask = self.safe_string(result, 10) askVolume = self.safe_string(result, 11) open = self.safe_string(result, 4) last = self.safe_string(result, 1) change = self.safe_string(result, 7) baseVolume = self.safe_string(result, 2) quoteVolume = self.safe_string_lower(result, 3) market = self.safe_market(marketId, market) symbol = market['symbol'] if timestamp is None: # ticker from WS has a different field(in seconds) timestamp = self.safe_integer_product(ticker, 's_t', 1000) if percentage is None: percentage = Precise.string_mul(change, '100') if quoteVolume is None: if baseVolume is None: # self is swap quoteVolume = self.safe_string(ticker, 'volume_24h', quoteVolume) else: # self is a ticker from websockets # contrary to name and documentation, base_volume_24h is actually the quote volume quoteVolume = baseVolume baseVolume = None return self.safe_ticker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'high': high, 'low': low, 'bid': bid, 'bidVolume': bidVolume, 'ask': ask, 'askVolume': askVolume, 'vwap': None, 'open': open, 'close': last, 'last': last, 'previousClose': None, 'change': None, 'percentage': percentage, 'average': average, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'indexPrice': self.safe_string(ticker, 'index_price'), 'info': ticker, }, market) def fetch_ticker(self, symbol: str, params={}) -> Ticker: """ fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market https://developer-pro.bitmart.com/en/spot/#get-ticker-of-a-trading-pair-v3 https://developer-pro.bitmart.com/en/futuresv2/#get-contract-details :param str symbol: unified symbol of the market to fetch the ticker for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `ticker structure ` """ self.load_markets() market = self.market(symbol) request: dict = {} response = None if market['swap']: request['symbol'] = market['id'] response = self.publicGetContractPublicDetails(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": { # "symbols": [ # { # "symbol": "BTCUSDT", # "product_type": 1, # "open_timestamp": 1645977600000, # "expire_timestamp": 0, # "settle_timestamp": 0, # "base_currency": "BTC", # "quote_currency": "USDT", # "last_price": "63547.4", # "volume_24h": "110938430", # "turnover_24h": "7004836342.6944", # "index_price": "63587.85404255", # "index_name": "BTCUSDT", # "contract_size": "0.001", # "min_leverage": "1", # "max_leverage": "100", # "price_precision": "0.1", # "vol_precision": "1", # "max_volume": "1000000", # "min_volume": "1", # "funding_rate": "0.0000801", # "expected_funding_rate": "-0.0000035", # "open_interest": "278214", # "open_interest_value": "17555316.9355496", # "high_24h": "64109.4", # "low_24h": "61857.6", # "change_24h": "0.0239264900886327", # "funding_time": 1726819200000 # }, # ] # } # } # elif market['spot']: request['symbol'] = market['id'] response = self.publicGetSpotQuotationV3Ticker(self.extend(request, params)) # # { # "code": 1000, # "trace": "f2194c2c202d2.99.1717535", # "message": "success", # "data": { # "symbol": "BTC_USDT", # "last": "68500.00", # "v_24h": "10491.65490", # "qv_24h": "717178990.42", # "open_24h": "68149.75", # "high_24h": "69499.99", # "low_24h": "67132.40", # "fluctuation": "0.00514", # "bid_px": "68500", # "bid_sz": "0.00162", # "ask_px": "68500.01", # "ask_sz": "0.01722", # "ts": "1717131391671" # } # } # else: raise NotSupported(self.id + ' fetchTicker() does not support ' + market['type'] + ' markets, only spot and swap markets are accepted') # fails in naming for contract tickers 'contract_symbol' tickers = [] ticker: dict = {} if market['spot']: ticker = self.safe_dict(response, 'data', {}) else: data = self.safe_dict(response, 'data', {}) tickers = self.safe_list(data, 'symbols', []) ticker = self.safe_dict(tickers, 0, {}) return self.parse_ticker(ticker, market) def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers: """ fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market https://developer-pro.bitmart.com/en/spot/#get-ticker-of-all-pairs-v3 https://developer-pro.bitmart.com/en/futuresv2/#get-contract-details :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a dictionary of `ticker structures ` """ self.load_markets() symbols = self.market_symbols(symbols) type = None market = None if symbols is not None: symbol = self.safe_string(symbols, 0) market = self.market(symbol) type, params = self.handle_market_type_and_params('fetchTickers', market, params) response = None if type == 'spot': response = self.publicGetSpotQuotationV3Tickers(params) # # { # "code": 1000, # "trace": "17c5e5d9ac49f9b71efca2bed55f1a.105.171225637482393", # "message": "success", # "data": [ # [ # "AFIN_USDT", # "0.001047", # "11110", # "11.632170", # "0.001048", # "0.001048", # "0.001047", # "-0.00095", # "0.001029", # "5555", # "0.001041", # "5297", # "1717122550482" # ], # ] # } # elif type == 'swap': response = self.publicGetContractPublicDetails(params) # # { # "code": 1000, # "message": "Ok", # "data": { # "symbols": [ # { # "symbol": "BTCUSDT", # "product_type": 1, # "open_timestamp": 1645977600000, # "expire_timestamp": 0, # "settle_timestamp": 0, # "base_currency": "BTC", # "quote_currency": "USDT", # "last_price": "63547.4", # "volume_24h": "110938430", # "turnover_24h": "7004836342.6944", # "index_price": "63587.85404255", # "index_name": "BTCUSDT", # "contract_size": "0.001", # "min_leverage": "1", # "max_leverage": "100", # "price_precision": "0.1", # "vol_precision": "1", # "max_volume": "1000000", # "min_volume": "1", # "funding_rate": "0.0000801", # "expected_funding_rate": "-0.0000035", # "open_interest": "278214", # "open_interest_value": "17555316.9355496", # "high_24h": "64109.4", # "low_24h": "61857.6", # "change_24h": "0.0239264900886327", # "funding_time": 1726819200000 # }, # ] # } # } # else: raise NotSupported(self.id + ' fetchTickers() does not support ' + type + ' markets, only spot and swap markets are accepted') tickers = [] if type == 'spot': tickers = self.safe_list(response, 'data', []) else: data = self.safe_dict(response, 'data', {}) tickers = self.safe_list(data, 'symbols', []) result: dict = {} for i in range(0, len(tickers)): ticker: dict = {} if type == 'spot': ticker = self.parse_ticker({'result': tickers[i]}) else: ticker = self.parse_ticker(tickers[i]) symbol = ticker['symbol'] result[symbol] = ticker return self.filter_by_array_tickers(result, 'symbol', symbols) def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook: """ fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data https://developer-pro.bitmart.com/en/spot/#get-depth-v3 https://developer-pro.bitmart.com/en/futuresv2/#get-market-depth :param str symbol: unified symbol of the market to fetch the order book for :param int [limit]: the maximum amount of order book entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: A dictionary of `order book structures ` indexed by market symbols """ self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } response = None if market['spot']: if limit is not None: request['limit'] = limit # default 35, max 50 response = self.publicGetSpotQuotationV3Books(self.extend(request, params)) elif market['swap']: response = self.publicGetContractPublicDepth(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchOrderBook() does not support ' + market['type'] + ' markets, only spot and swap markets are accepted') # # spot # # { # "code": 1000, # "message": "success", # "data": { # "ts": "1695264191808", # "symbol": "BTC_USDT", # "asks": [ # ["26942.57","0.06492"], # ["26942.73","0.05447"], # ["26943.00","0.07154"] # ], # "bids": [ # ["26942.45","0.00074"], # ["26941.53","0.00371"], # ["26940.94","0.08992"] # ] # }, # "trace": "430a7f69581d4258a8e4b424dfb10782.73.16952341919017619" # } # # swap # # { # "code": 1000, # "message": "Ok", # "data": { # "asks": [ # ["26938.3","3499","3499"], # ["26938.5","14702","18201"], # ["26938.6","20457","38658"] # ], # "bids": [ # ["26938.2","20","20"], # ["26937.9","1913","1933"], # ["26937.8","2588","4521"] # ], # "timestamp": 1695264383999, # "symbol": "BTCUSDT" # }, # "trace": "4cad855074664097ac6ba5258c47305d.72.16952643834721135" # } # data = self.safe_dict(response, 'data', {}) timestamp = self.safe_integer_2(data, 'ts', 'timestamp') return self.parse_order_book(data, market['symbol'], timestamp) def parse_trade(self, trade: dict, market: Market = None) -> Trade: # # public fetchTrades spot( amount = count * price ) # # [ # "BTC_USDT", # symbol # "1717212457302", # timestamp # "67643.11", # price # "0.00106", # size # "sell" # side # ] # # spot: fetchMyTrades # # { # "tradeId":"182342999769370687", # "orderId":"183270218784142990", # "clientOrderId":"183270218784142990", # "symbol":"ADA_USDT", # "side":"buy", # "orderMode":"spot", # "type":"market", # "price":"0.245948", # "size":"20.71", # "notional":"5.09358308", # "fee":"0.00509358", # "feeCoinName":"USDT", # "tradeRole":"taker", # "createTime":1695658457836, # } # # swap: fetchMyTrades # # { # "order_id": "230930336848609", # "trade_id": "6212604014", # "symbol": "BTCUSDT", # "side": 3, # "price": "26910.4", # "vol": "1", # "exec_type": "Taker", # "profit": False, # "create_time": 1695961596692, # "realised_profit": "-0.0003", # "paid_fees": "0.01614624" # } # # ws swap # # { # 'fee': '-0.000044502', # 'feeCcy': 'USDT', # 'fillPrice': '74.17', # 'fillQty': '1', # 'lastTradeID': 6802340762 # } # timestamp = self.safe_integer_n(trade, ['createTime', 'create_time', 1]) isPublic = self.safe_string(trade, 0) isPublicTrade = (isPublic is not None) amount = None cost = None type = None side = None if isPublicTrade: amount = self.safe_string_2(trade, 'count', 3) cost = self.safe_string(trade, 'amount') side = self.safe_string_2(trade, 'type', 4) else: amount = self.safe_string_n(trade, ['size', 'vol', 'fillQty']) cost = self.safe_string(trade, 'notional') type = self.safe_string(trade, 'type') side = self.parse_order_side(self.safe_string(trade, 'side')) marketId = self.safe_string_2(trade, 'symbol', 0) market = self.safe_market(marketId, market) feeCostString = self.safe_string_2(trade, 'fee', 'paid_fees') fee = None if feeCostString is not None: feeCurrencyId = self.safe_string(trade, 'feeCoinName') 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': self.safe_string_n(trade, ['tradeId', 'trade_id', 'lastTradeID']), 'order': self.safe_string_2(trade, 'orderId', 'order_id'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'symbol': market['symbol'], 'type': type, 'side': side, 'price': self.safe_string_n(trade, ['price', 'fillPrice', 2]), 'amount': amount, 'cost': cost, 'takerOrMaker': self.safe_string_lower_2(trade, 'tradeRole', 'exec_type'), 'fee': fee, }, market) def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]: """ get a list of the most recent trades for a particular symbol https://developer-pro.bitmart.com/en/spot/#get-recent-trades-v3 :param str symbol: unified symbol of the market to fetch trades for :param int [since]: timestamp in ms of the earliest trade to fetch :param int [limit]: the maximum number of trades to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns Trade[]: a list of `trade structures ` """ 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: dict = { 'symbol': market['id'], } if limit is not None: request['limit'] = limit response = self.publicGetSpotQuotationV3Trades(self.extend(request, params)) # # { # "code": 1000, # "trace": "58031f9a5bd.111.17117", # "message": "success", # "data": [ # [ # "BTC_USDT", # "1717212457302", # "67643.11", # "0.00106", # "sell" # ], # ] # } # data = self.safe_list(response, 'data', []) return self.parse_trades(data, market, since, limit) def parse_ohlcv(self, ohlcv, market: Market = None) -> list: # # spot # [ # "1699512060", # timestamp # "36746.49", # open # "36758.71", # high # "36736.13", # low # "36755.99", # close # "2.83965", # base volume # "104353.57" # quote volume # ] # # 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", # open # "46555.71", # high # "46511.41", # low # "46555.71", # close # "0.25", # volume # ] # # ws swap # { # "symbol":"BTCUSDT", # "o":"146.24", # "h":"146.24", # "l":"146.24", # "c":"146.24", # "v":"146" # } # 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_2(ohlcv, 'timestamp', 'ts'), self.safe_number_2(ohlcv, 'open_price', 'o'), self.safe_number_2(ohlcv, 'high_price', 'h'), self.safe_number_2(ohlcv, 'low_price', 'l'), self.safe_number_2(ohlcv, 'close_price', 'c'), self.safe_number_2(ohlcv, 'volume', 'v'), ] def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]: """ fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market https://developer-pro.bitmart.com/en/spot/#get-history-k-line-v3 https://developer-pro.bitmart.com/en/futuresv2/#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 [since]: timestamp in ms of the earliest candle to fetch :param int [limit]: the maximum amount of candles to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: timestamp of the latest candle in ms :param boolean [params.paginate]: *spot only* default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params) :returns int[][]: A list of candles ordered, open, high, low, close, volume """ self.load_markets() paginate = False paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False) if paginate: return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 200) market = self.market(symbol) duration = self.parse_timeframe(timeframe) parsedTimeframe = self.safe_integer(self.timeframes, timeframe) request: dict = { 'symbol': market['id'], } if parsedTimeframe is not None: request['step'] = parsedTimeframe else: request['step'] = timeframe if market['spot']: request, params = self.handle_until_option('before', request, params, 0.001) if limit is not None: request['limit'] = limit if since is not None: request['after'] = self.parse_to_int((since / 1000)) - 1 else: maxLimit = 500 if limit is None: limit = maxLimit limit = min(maxLimit, limit) now = self.parse_to_int(self.milliseconds() / 1000) if since is None: start = now - limit * duration request['start_time'] = start request['end_time'] = now else: start = self.parse_to_int((since / 1000)) - 1 end = self.sum(start, limit * duration) request['start_time'] = start request['end_time'] = min(end, now) request, params = self.handle_until_option('end_time', request, params, 0.001) response = None if market['swap']: price = self.safe_string(params, 'price') if price == 'mark': params = self.omit(params, 'price') response = self.publicGetContractPublicMarkpriceKline(self.extend(request, params)) else: response = self.publicGetContractPublicKline(self.extend(request, params)) else: response = self.publicGetSpotQuotationV3Klines(self.extend(request, params)) # # spot # # { # "code": 1000, # "message": "success", # "data": [ # ["1699512060","36746.49","36758.71","36736.13","36755.99","2.83965","104353.57"], # ["1699512120","36756.00","36758.70","36737.14","36737.63","1.96070","72047.10"], # ["1699512180","36737.63","36740.45","36737.62","36740.44","0.63194","23217.62"] # ], # "trace": "6591fc7b508845359d5fa442e3b3a4fb.72.16995122398750695" # } # # 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" # } # ohlcv = self.safe_list(response, 'data', []) return self.parse_ohlcvs(ohlcv, market, timeframe, since, limit) def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ https://developer-pro.bitmart.com/en/spot/#account-trade-list-v4-signed https://developer-pro.bitmart.com/en/futuresv2/#get-order-trade-keyed fetch all trades made by the user :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: the latest time in ms to fetch trades for :param boolean [params.marginMode]: *spot* whether to fetch trades for margin orders or spot orders, defaults to spot orders(only isolated margin orders are supported) :param str [params.stpMode]: self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both'] :returns Trade[]: a list of `trade structures ` """ self.load_markets() market = None request: dict = {} if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] type = None response = None type, params = self.handle_market_type_and_params('fetchMyTrades', market, params) until = self.safe_integer_n(params, ['until', 'endTime', 'end_time']) params = self.omit(params, ['until']) if type == 'spot': marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchMyTrades', params) if marginMode == 'isolated': request['orderMode'] = 'iso_margin' options = self.safe_dict(self.options, 'fetchMyTrades', {}) maxLimit = 200 defaultLimit = self.safe_integer(options, 'limit', maxLimit) if limit is None: limit = defaultLimit request['limit'] = min(limit, maxLimit) if since is not None: request['startTime'] = since if until is not None: request['endTime'] = until response = self.privatePostSpotV4QueryTrades(self.extend(request, params)) elif type == 'swap': if symbol is None: raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument') if since is not None: request['start_time'] = since if until is not None: request['end_time'] = until response = self.privateGetContractPrivateTrades(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchMyTrades() does not support ' + type + ' orders, only spot and swap orders are accepted') # # spot # # { # "code":1000, # "message":"success", # "data":[ # { # "tradeId":"182342999769370687", # "orderId":"183270218784142990", # "clientOrderId":"183270218784142990", # "symbol":"ADA_USDT", # "side":"buy", # "orderMode":"spot", # "type":"market", # "price":"0.245948", # "size":"20.71", # "notional":"5.09358308", # "fee":"0.00509358", # "feeCoinName":"USDT", # "tradeRole":"taker", # "createTime":1695658457836, # "updateTime":1695658457836 # } # ], # "trace":"fbaee9e0e2f5442fba5b3262fc86b0ac.65.16956593456523085" # } # # swap # # { # "code": 1000, # "message": "Ok", # "data": [ # { # "order_id": "230930336848609", # "trade_id": "6212604014", # "symbol": "BTCUSDT", # "side": 3, # "price": "26910.4", # "vol": "1", # "exec_type": "Taker", # "profit": False, # "create_time": 1695961596692, # "realised_profit": "-0.0003", # "paid_fees": "0.01614624" # }, # ], # "trace": "4cad855074634097ac6ba5257c47305d.62.16959616054873723" # } # data = self.safe_list(response, 'data', []) return self.parse_trades(data, market, since, limit) def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ https://developer-pro.bitmart.com/en/spot/#order-trade-list-v4-signed fetch all the trades made from a single order :param str id: order id :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch trades for :param int [limit]: the maximum number of trades to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.stpMode]: self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both'] :returns dict[]: a list of `trade structures ` """ self.load_markets() request: dict = { 'orderId': id, } response = self.privatePostSpotV4QueryOrderTrades(self.extend(request, params)) data = self.safe_list(response, 'data', []) return self.parse_trades(data, None, since, limit) def custom_parse_balance(self, response, marketType) -> Balances: data = self.safe_dict(response, 'data', {}) wallet = None if marketType == 'swap': wallet = self.safe_list(response, 'data', []) elif marketType == 'margin': wallet = self.safe_list(data, 'symbols', []) else: wallet = self.safe_list(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_dict(entry, 'base', {}) quote = self.safe_dict(entry, 'quote', {}) baseCode = self.safe_currency_code(self.safe_string(base, 'currency')) quoteCode = self.safe_currency_code(self.safe_string(quote, 'currency')) subResult: dict = {} 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_n(balance, ['unAvailable', '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={}) -> Balances: """ query for balance and get the amount of funds available for trading or funds locked in orders https://developer-pro.bitmart.com/en/spot/#get-spot-wallet-balance-keyed https://developer-pro.bitmart.com/en/futuresv2/#get-contract-assets-keyed https://developer-pro.bitmart.com/en/spot/#get-account-balance-keyed https://developer-pro.bitmart.com/en/spot/#get-margin-account-details-isolated-keyed :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `balance structure ` """ self.load_markets() marketType = None marketType, params = self.handle_market_type_and_params('fetchBalance', None, params) marginMode = self.safe_string(params, 'marginMode') isMargin = self.safe_bool(params, 'margin', False) params = self.omit(params, ['margin', 'marginMode']) if marginMode is not None or isMargin: marketType = 'margin' response = None if marketType == 'spot': response = self.privateGetSpotV1Wallet(params) elif marketType == 'swap': response = self.privateGetContractPrivateAssetsDetail(params) elif marketType == 'account': response = self.privateGetAccountV1Wallet(params) elif marketType == 'margin': response = self.privateGetSpotV1MarginIsolatedAccount(params) else: raise NotSupported(self.id + ' fetchBalance() does not support ' + marketType + ' markets, only spot, swap and account and margin markets are accepted') # # 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","available_usd_valuation":null}, # {"currency":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000","available_usd_valuation":null} # ] # } # } # # 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.custom_parse_balance(response, marketType) def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface: # # { # "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'), 'percentage': None, 'tierBased': None, } def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface: """ fetch the trading fees for a market https://developer-pro.bitmart.com/en/spot/#get-actual-trade-fee-rate-keyed :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `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: dict = { '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_dict(response, 'data', {}) return self.parse_trading_fee(data) def parse_order(self, order: dict, market: Market = None) -> Order: # # createOrder, editOrder # # { # "order_id": 2707217580 # } # # swap # "data": { # "order_id": 231116359426639, # "price": "market price" # }, # # 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" # } # # spot v4 # { # "orderId" : "118100034543076010", # "clientOrderId" : "118100034543076010", # "symbol" : "BTC_USDT", # "side" : "buy", # "orderMode" : "spot", # "type" : "limit", # "state" : "filled", # "price" : "48800.00", # "priceAvg" : "39999.00", # "size" : "0.10000", # "filledSize" : "0.10000", # "notional" : "4880.00000000", # "filledNotional" : "3999.90000000", # "createTime" : 1681701557927, # "updateTime" : 1681701559408 # } # # swap: fetchOrder, fetchOpenOrders, fetchClosedOrders # # { # "order_id": "230935812485489", # "client_order_id": "", # "price": "24000", # "size": "1", # "symbol": "BTCUSDT", # "state": 2, # "side": 1, # "type": "limit", # "leverage": "10", # "open_type": "isolated", # "deal_avg_price": "0", # "deal_size": "0", # "create_time": 1695702258629, # "update_time": 1695702258642, # "activation_price_type": 0, # "activation_price": "", # "callback_rate": "" # } # id = None if isinstance(order, str): id = order order = {} id = self.safe_string_2(order, 'order_id', 'orderId', id) timestamp = self.safe_integer_2(order, 'create_time', 'createTime') marketId = self.safe_string(order, 'symbol') symbol = self.safe_symbol(marketId, market) market = self.safe_market(symbol, market) orderType = self.safe_string(market, 'type', 'spot') 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' priceString = self.safe_string(order, 'price') if priceString == 'market price': priceString = None trailingActivationPrice = self.safe_number(order, 'activation_price') return self.safe_order({ 'id': id, 'clientOrderId': self.safe_string_2(order, 'client_order_id', 'clientOrderId'), 'info': order, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastTradeTimestamp': self.safe_integer(order, 'update_time'), 'symbol': symbol, 'type': type, 'timeInForce': timeInForce, 'postOnly': postOnly, 'side': self.parse_order_side(self.safe_string(order, 'side')), 'price': self.omit_zero(priceString), 'triggerPrice': trailingActivationPrice, 'amount': self.omit_zero(self.safe_string(order, 'size')), 'cost': self.safe_string_2(order, 'filled_notional', 'filledNotional'), 'average': self.safe_string_n(order, ['price_avg', 'priceAvg', 'deal_avg_price']), 'filled': self.safe_string_n(order, ['filled_size', 'filledSize', 'deal_size']), 'remaining': None, 'status': self.parse_order_status_by_type(orderType, self.safe_string_2(order, 'status', 'state')), 'fee': None, 'trades': None, }, market) def parse_order_side(self, side): sides: dict = { '1': 'buy', '2': 'buy', '3': 'sell', '4': 'sell', } return self.safe_string(sides, side, side) def parse_order_status_by_type(self, type, status): statusesByType: dict = { 'spot': { '1': 'rejected', # Order failure '2': 'open', # Placing order '3': 'rejected', # Order failure, Freeze failure '4': 'open', # Order success, Pending for fulfilment '5': 'open', # Partially filled '6': 'closed', # Fully filled '7': 'canceled', # Canceling '8': 'canceled', # Canceled 'new': 'open', 'partially_filled': 'open', 'filled': 'closed', 'partially_canceled': 'canceled', }, 'swap': { '1': 'open', # Submitting '2': 'open', # Commissioned '4': 'closed', # Completed }, } statuses = self.safe_dict(statusesByType, type, {}) return self.safe_string(statuses, status, status) def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}): """ create a market buy order by providing the symbol and cost https://developer-pro.bitmart.com/en/spot/#new-order-v2-signed :param str symbol: unified symbol of the market to create an order in :param float cost: how much you want to trade in units of the quote currency :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) if not market['spot']: raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only') params['createMarketBuyOrderRequiresPrice'] = False return self.create_order(symbol, 'market', 'buy', cost, None, params) def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ create a trade order https://developer-pro.bitmart.com/en/spot/#new-order-v2-signed https://developer-pro.bitmart.com/en/spot/#new-margin-order-v1-signed https://developer-pro.bitmart.com/en/futuresv2/#submit-order-signed https://developer-pro.bitmart.com/en/futuresv2/#submit-plan-order-signed https://developer-pro.bitmart.com/en/futuresv2/#submit-tp-sl-order-signed https://developer-pro.bitmart.com/en/futuresv2/#submit-trail-order-signed :param str symbol: unified symbol of the market to create an order in :param str type: 'market', 'limit' or 'trailing' for swap markets only :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: 'cross' or 'isolated' :param str [params.leverage]: *swap only* leverage level :param str [params.clientOrderId]: client order id of the order :param boolean [params.reduceOnly]: *swap only* reduce only :param boolean [params.postOnly]: make sure the order is posted to the order book and not matched immediately :param str [params.triggerPrice]: *swap only* the price to trigger a stop order :param int [params.price_type]: *swap only* 1: last price, 2: fair price, default is 1 :param int [params.price_way]: *swap only* 1: price way long, 2: price way short :param int [params.activation_price_type]: *swap trailing order only* 1: last price, 2: fair price, default is 1 :param str [params.trailingPercent]: *swap only* the percent to trail away from the current market price, min 0.1 max 5 :param str [params.trailingTriggerPrice]: *swap only* the price to trigger a trailing order, default uses the price argument :param str [params.stopLossPrice]: *swap only* the price to trigger a stop-loss order :param str [params.takeProfitPrice]: *swap only* the price to trigger a take-profit order :param int [params.plan_category]: *swap tp/sl only* 1: tp/sl, 2: position tp/sl, default is 1 :param str [params.stpMode]: self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both'] :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) result = self.handle_margin_mode_and_params('createOrder', params) marginMode = self.safe_string(result, 0) triggerPrice = self.safe_string_n(params, ['triggerPrice', 'stopPrice', 'trigger_price']) stopLossPrice = self.safe_string(params, 'stopLossPrice') takeProfitPrice = self.safe_string(params, 'takeProfitPrice') isStopLoss = stopLossPrice is not None isTakeProfit = takeProfitPrice is not None isTriggerOrder = triggerPrice is not None response = None if market['spot']: spotRequest = self.create_spot_order_request(symbol, type, side, amount, price, params) if marginMode == 'isolated': response = self.privatePostSpotV1MarginSubmitOrder(spotRequest) else: response = self.privatePostSpotV2SubmitOrder(spotRequest) else: swapRequest = self.create_swap_order_request(symbol, type, side, amount, price, params) activationPrice = self.safe_string(swapRequest, 'activation_price') if activationPrice is not None: # if type is trailing response = self.privatePostContractPrivateSubmitTrailOrder(swapRequest) elif isTriggerOrder: response = self.privatePostContractPrivateSubmitPlanOrder(swapRequest) elif isStopLoss or isTakeProfit: response = self.privatePostContractPrivateSubmitTpSlOrder(swapRequest) else: response = self.privatePostContractPrivateSubmitOrder(swapRequest) # # spot and margin # # { # "code": 1000, # "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", # "message": "OK", # "data": { # "order_id": 2707217580 # } # } # # swap # {"code":1000,"message":"Ok","data":{"order_id":231116359426639,"price":"market price"},"trace":"7f9c94e10f9d4513bc08a7bfc2a5559a.62.16996369620521911"} # data = self.safe_dict(response, 'data', {}) order = self.parse_order(data, market) order['type'] = type order['side'] = side order['amount'] = amount order['price'] = price return order def create_orders(self, orders: List[OrderRequest], params={}): """ create a list of trade orders https://developer-pro.bitmart.com/en/spot/#new-batch-order-v4-signed :param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type, side, amount, price and params :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.stpMode]: self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both'] :returns dict: an `order structure ` """ self.load_markets() ordersRequests = [] symbol = None market = None for i in range(0, len(orders)): rawOrder = orders[i] marketId = self.safe_string(rawOrder, 'symbol') market = self.market(marketId) if not market['spot']: raise NotSupported(self.id + ' createOrders() supports spot orders only') if symbol is None: symbol = marketId else: if symbol != marketId: raise BadRequest(self.id + ' createOrders() requires all orders to have the same symbol') type = self.safe_string(rawOrder, 'type') side = self.safe_string(rawOrder, 'side') amount = self.safe_value(rawOrder, 'amount') price = self.safe_value(rawOrder, 'price') orderParams = self.safe_dict(rawOrder, 'params', {}) orderRequest = self.create_spot_order_request(marketId, type, side, amount, price, orderParams) orderRequest = self.omit(orderRequest, ['symbol']) # not needed because it goes in the outter object ordersRequests.append(orderRequest) request: dict = { 'symbol': market['id'], 'orderParams': ordersRequests, } response = self.privatePostSpotV4BatchOrders(request) # # { # "message": "OK", # "code": 1000, # "trace": "5fc697fb817a4b5396284786a9b2609a.263.17022620476480263", # "data": { # "code": 0, # "msg": "success", # "data": { # "orderIds": [ # "212751308355553320" # ] # } # } # } # data = self.safe_dict(response, 'data', {}) innderData = self.safe_dict(data, 'data', {}) orderIds = self.safe_list(innderData, 'orderIds', []) parsedOrders = [] for i in range(0, len(orderIds)): orderId = orderIds[i] order = self.safe_order({'id': orderId}, market) parsedOrders.append(order) return parsedOrders def create_swap_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ @ignore create a trade order https://developer-pro.bitmart.com/en/futuresv2/#submit-order-signed https://developer-pro.bitmart.com/en/futuresv2/#submit-plan-order-signed https://developer-pro.bitmart.com/en/futuresv2/#submit-tp-sl-order-signed https://developer-pro.bitmart.com/en/futuresv2/#submit-trail-order-signed :param str symbol: unified symbol of the market to create an order in :param str type: 'market', 'limit', 'trailing', 'stop_loss', or 'take_profit' :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.leverage]: leverage level :param boolean [params.reduceOnly]: *swap only* reduce only :param str [params.marginMode]: 'cross' or 'isolated', default is 'cross' :param str [params.clientOrderId]: client order id of the order :param str [params.triggerPrice]: *swap only* the price to trigger a stop order :param int [params.price_type]: *swap only* 1: last price, 2: fair price, default is 1 :param int [params.price_way]: *swap only* 1: price way long, 2: price way short :param int [params.activation_price_type]: *swap trailing order only* 1: last price, 2: fair price, default is 1 :param str [params.trailingPercent]: *swap only* the percent to trail away from the current market price, min 0.1 max 5 :param str [params.trailingTriggerPrice]: *swap only* the price to trigger a trailing order, default uses the price argument :param str [params.stopLossPrice]: *swap only* the price to trigger a stop-loss order :param str [params.takeProfitPrice]: *swap only* the price to trigger a take-profit order :param int [params.plan_category]: *swap tp/sl only* 1: tp/sl, 2: position tp/sl, default is 1 :returns dict: an `order structure ` """ market = self.market(symbol) stopLossPrice = self.safe_string(params, 'stopLossPrice') takeProfitPrice = self.safe_string(params, 'takeProfitPrice') isStopLoss = stopLossPrice is not None isTakeProfit = takeProfitPrice is not None if isStopLoss: type = 'stop_loss' elif isTakeProfit: type = 'take_profit' request: dict = { 'symbol': market['id'], 'size': int(self.amount_to_precision(symbol, amount)), } timeInForce = self.safe_string(params, 'timeInForce') mode = self.safe_integer(params, 'mode') # only for swap isMarketOrder = type == 'market' postOnly = None reduceOnly = self.safe_bool(params, 'reduceOnly') isExchangeSpecificPo = (mode == 4) postOnly, params = self.handle_post_only(isMarketOrder, isExchangeSpecificPo, params) ioc = ((timeInForce == 'IOC') or (mode == 3)) isLimitOrder = (type == 'limit') or postOnly or ioc if timeInForce == 'GTC': request['mode'] = 1 elif timeInForce == 'FOK': request['mode'] = 2 elif timeInForce == 'IOC': request['mode'] = 3 if postOnly: request['mode'] = 4 triggerPrice = self.safe_string_n(params, ['triggerPrice', 'stopPrice', 'trigger_price']) isTriggerOrder = triggerPrice is not None trailingTriggerPrice = self.safe_string_2(params, 'trailingTriggerPrice', 'activation_price', self.number_to_string(price)) trailingPercent = self.safe_string_2(params, 'trailingPercent', 'callback_rate') isTrailingPercentOrder = trailingPercent is not None if isLimitOrder: request['price'] = self.price_to_precision(symbol, price) elif type == 'trailing' or isTrailingPercentOrder: type = 'trailing' request['callback_rate'] = trailingPercent request['activation_price'] = self.price_to_precision(symbol, trailingTriggerPrice) request['activation_price_type'] = self.safe_integer(params, 'activation_price_type', 1) if isTriggerOrder: if isLimitOrder or price is not None: request['executive_price'] = self.price_to_precision(symbol, price) request['trigger_price'] = self.price_to_precision(symbol, triggerPrice) request['price_type'] = self.safe_integer(params, 'price_type', 1) if side == 'buy': if reduceOnly: request['price_way'] = 2 else: request['price_way'] = 1 elif side == 'sell': if reduceOnly: request['price_way'] = 1 else: request['price_way'] = 2 marginMode = None marginMode, params = self.handle_margin_mode_and_params('createOrder', params, 'cross') if isStopLoss or isTakeProfit: reduceOnly = True request['price_type'] = self.safe_integer(params, 'price_type', 1) request['executive_price'] = self.price_to_precision(symbol, price) if isStopLoss: request['trigger_price'] = self.price_to_precision(symbol, stopLossPrice) else: request['trigger_price'] = self.price_to_precision(symbol, takeProfitPrice) else: request['open_type'] = marginMode if side == 'buy': if reduceOnly: request['side'] = 2 # buy close short else: request['side'] = 1 # buy open long elif side == 'sell': if reduceOnly: request['side'] = 3 # sell close long else: request['side'] = 4 # sell open short clientOrderId = self.safe_string(params, 'clientOrderId') if clientOrderId is not None: params = self.omit(params, 'clientOrderId') request['client_order_id'] = clientOrderId leverage = self.safe_integer(params, 'leverage') params = self.omit(params, ['timeInForce', 'postOnly', 'reduceOnly', 'leverage', 'trailingTriggerPrice', 'trailingPercent', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice']) if leverage is not None: request['leverage'] = self.number_to_string(leverage) elif isTriggerOrder: request['leverage'] = '1' # for plan orders leverage is required, if not available default to 1 if type != 'trailing': request['type'] = type return self.extend(request, params) def create_spot_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}): """ @ignore create a spot order request https://developer-pro.bitmart.com/en/spot/#new-order-v2-signed https://developer-pro.bitmart.com/en/spot/#new-margin-order-v1-signed :param str symbol: unified symbol of the market to create an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float amount: how much of currency you want to trade in units of base currency :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: 'cross' or 'isolated' :returns dict: an `order structure ` """ market = self.market(symbol) request: dict = { 'symbol': market['id'], 'side': side, 'type': type, } timeInForce = self.safe_string(params, 'timeInForce') if timeInForce == 'FOK': raise InvalidOrder(self.id + ' createOrder() only accepts timeInForce parameter values of IOC or PO') mode = self.safe_integer(params, 'mode') # only for swap isMarketOrder = type == 'market' postOnly = None isExchangeSpecificPo = (type == 'limit_maker') or (mode == 4) postOnly, params = self.handle_post_only(isMarketOrder, isExchangeSpecificPo, params) params = self.omit(params, ['timeInForce', 'postOnly']) ioc = ((timeInForce == 'IOC') or (type == 'ioc')) isLimitOrder = (type == 'limit') or postOnly or ioc # 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_string_2(params, 'cost', 'notional') params = self.omit(params, 'cost') createMarketBuyOrderRequiresPrice = True createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True) if createMarketBuyOrderRequiresPrice: if (price is None) and (notional is None): raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend in the amount argument or in the "notional" extra parameter(the exchange-specific behaviour)') else: amountString = self.number_to_string(amount) priceString = self.number_to_string(price) notional = Precise.string_mul(amountString, priceString) else: notional = self.number_to_string(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) if postOnly: request['type'] = 'limit_maker' if ioc: request['type'] = 'ioc' clientOrderId = self.safe_string(params, 'clientOrderId') if clientOrderId is not None: params = self.omit(params, 'clientOrderId') request['client_order_id'] = clientOrderId return self.extend(request, params) def cancel_order(self, id: str, symbol: Str = None, params={}): """ cancels an open order https://developer-pro.bitmart.com/en/futuresv2/#cancel-order-signed https://developer-pro.bitmart.com/en/spot/#cancel-order-v3-signed https://developer-pro.bitmart.com/en/futuresv2/#cancel-plan-order-signed https://developer-pro.bitmart.com/en/futuresv2/#cancel-order-signed https://developer-pro.bitmart.com/en/futuresv2/#cancel-trail-order-signed :param str id: order id :param str symbol: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.clientOrderId]: *spot only* the client order id of the order to cancel :param boolean [params.trigger]: *swap only* whether the order is a trigger order :param boolean [params.trailing]: *swap only* whether the order is a stop order :returns dict: An `order structure ` """ if symbol is None: raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument') self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_order_id') if clientOrderId is not None: request['client_order_id'] = clientOrderId else: request['order_id'] = str(id) params = self.omit(params, ['clientOrderId']) response = None if market['spot']: response = self.privatePostSpotV3CancelOrder(self.extend(request, params)) else: trigger = self.safe_bool_2(params, 'stop', 'trigger') trailing = self.safe_bool(params, 'trailing') params = self.omit(params, ['stop', 'trigger']) if trigger: response = self.privatePostContractPrivateCancelPlanOrder(self.extend(request, params)) elif trailing: response = self.privatePostContractPrivateCancelTrailOrder(self.extend(request, params)) else: response = self.privatePostContractPrivateCancelOrder(self.extend(request, params)) # swap # {"code":1000,"message":"Ok","trace":"7f9c94e10f9d4513bc08a7bfc2a5559a.55.16959817848001851"} # # 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 # } # if market['swap']: return self.safe_order({'info': response}) data = self.safe_value(response, 'data') if data is True: return self.safe_order({'id': 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.safe_order({'id': id, 'symbol': market['symbol'], 'info': {}}, market) return order def cancel_orders(self, ids: List[str], symbol: Str = None, params={}) -> List[Order]: """ cancel multiple orders https://developer-pro.bitmart.com/en/spot/#cancel-batch-order-v4-signed :param str[] ids: order ids :param str symbol: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :param str[] [params.clientOrderIds]: client order ids :returns dict: an list of `order structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument') self.load_markets() market = self.market(symbol) if not market['spot']: raise NotSupported(self.id + ' cancelOrders() does not support ' + market['type'] + ' orders, only spot orders are accepted') clientOrderIds = self.safe_list(params, 'clientOrderIds') params = self.omit(params, ['clientOrderIds']) request: dict = { 'symbol': market['id'], } if clientOrderIds is not None: request['clientOrderIds'] = clientOrderIds else: request['orderIds'] = ids response = self.privatePostSpotV4CancelOrders(self.extend(request, params)) # # { # "message": "OK", # "code": 1000, # "trace": "c4edbce860164203954f7c3c81d60fc6.309.17022669632770001", # "data": { # "successIds": [ # "213055379155243012" # ], # "failIds": [], # "totalCount": 1, # "successCount": 1, # "failedCount": 0 # } # } # data = self.safe_dict(response, 'data', {}) allOrders = [] successIds = self.safe_list(data, 'successIds', []) for i in range(0, len(successIds)): id = successIds[i] allOrders.append(self.safe_order({'id': id, 'status': 'canceled'}, market)) failIds = self.safe_list(data, 'failIds', []) for i in range(0, len(failIds)): id = failIds[i] allOrders.append(self.safe_order({'id': id, 'status': 'failed'}, market)) return allOrders def cancel_all_orders(self, symbol: Str = None, params={}): """ cancel all open orders in a market https://developer-pro.bitmart.com/en/spot/#cancel-all-order-v4-signed https://developer-pro.bitmart.com/en/futuresv2/#cancel-all-orders-signed :param str symbol: unified market symbol of the market to cancel orders in :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.side]: *spot only* 'buy' or 'sell' :returns dict[]: a list of `order structures ` """ self.load_markets() request: dict = {} market = None if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] response = None type = None type, params = self.handle_market_type_and_params('cancelAllOrders', market, params) if type == 'spot': response = self.privatePostSpotV4CancelAll(self.extend(request, params)) elif type == 'swap': if symbol is None: raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument') response = self.privatePostContractPrivateCancelOrders(self.extend(request, params)) # # spot # # { # "code": 1000, # "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", # "message": "OK", # "data": {} # } # # swap # # { # "code": 1000, # "message": "Ok", # "trace": "7f9c94e10f9d4513bc08a7bfc2a5559a.70.16954131323145323" # } # return [self.safe_order({'info': response})] def fetch_orders_by_status(self, status, symbol: Str = None, since: Int = None, limit: Int = 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: dict = { '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_dict(response, 'data', {}) orders = self.safe_list(data, 'orders', []) return self.parse_orders(orders, market, since, limit) def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://developer-pro.bitmart.com/en/spot/#current-open-orders-v4-signed https://developer-pro.bitmart.com/en/futuresv2/#get-all-open-orders-keyed https://developer-pro.bitmart.com/en/futuresv2/#get-all-current-plan-orders-keyed fetch all unfilled currently open orders :param str symbol: unified market symbol :param int [since]: the earliest time in ms to fetch open orders for :param int [limit]: the maximum number of open order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param boolean [params.marginMode]: *spot* whether to fetch trades for margin orders or spot orders, defaults to spot orders(only isolated margin orders are supported) :param int [params.until]: *spot* the latest time in ms to fetch orders for :param str [params.type]: *swap* order type, 'limit' or 'market' :param str [params.order_state]: *swap* the order state, 'all' or 'partially_filled', default is 'all' :param str [params.orderType]: *swap only* 'limit', 'market', or 'trailing' :param boolean [params.trailing]: *swap only* set to True if you want to fetch trailing orders :param boolean [params.trigger]: *swap only* set to True if you want to fetch trigger orders :param str [params.stpMode]: self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both'] :returns Order[]: a list of `order structures ` """ self.load_markets() market = None request: dict = {} if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] type = None response = None type, params = self.handle_market_type_and_params('fetchOpenOrders', market, params) if type == 'spot': if limit is not None: request['limit'] = min(limit, 200) marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchOpenOrders', params) if marginMode == 'isolated': request['orderMode'] = 'iso_margin' if since is not None: request['startTime'] = since until = self.safe_integer_2(params, 'until', 'endTime') if until is not None: params = self.omit(params, ['endTime']) request['endTime'] = until response = self.privatePostSpotV4QueryOpenOrders(self.extend(request, params)) elif type == 'swap': if limit is not None: request['limit'] = min(limit, 100) isTrigger = self.safe_bool_2(params, 'stop', 'trigger') params = self.omit(params, ['stop', 'trigger']) if isTrigger: response = self.privateGetContractPrivateCurrentPlanOrder(self.extend(request, params)) else: trailing = self.safe_bool(params, 'trailing', False) orderType = self.safe_string(params, 'orderType') params = self.omit(params, ['orderType', 'trailing']) if trailing: orderType = 'trailing' if orderType is not None: request['type'] = orderType response = self.privateGetContractPrivateGetOpenOrders(self.extend(request, params)) else: raise NotSupported(self.id + ' fetchOpenOrders() does not support ' + type + ' orders, only spot and swap orders are accepted') # # spot # # { # "code": 1000, # "message": "success", # "data": [ # { # "orderId": "183299373022163211", # "clientOrderId": "183299373022163211", # "symbol": "BTC_USDT", # "side": "buy", # "orderMode": "spot", # "type": "limit", # "state": "new", # "price": "25000.00", # "priceAvg": "0.00", # "size": "0.00020", # "filledSize": "0.00000", # "notional": "5.00000000", # "filledNotional": "0.00000000", # "createTime": 1695703703338, # "updateTime": 1695703703359 # } # ], # "trace": "15f11d48e3234c81a2e786cr2e7a38e6.71.16957022303515933" # } # # swap # # { # "code": 1000, # "message": "Ok", # "data": [ # { # "order_id": "230935812485489", # "client_order_id": "", # "price": "24000", # "size": "1", # "symbol": "BTCUSDT", # "state": 2, # "side": 1, # "type": "limit", # "leverage": "10", # "open_type": "isolated", # "deal_avg_price": "0", # "deal_size": "0", # "create_time": 1695702258629, # "update_time": 1695702258642 # } # ], # "trace": "7f9d94g10f9d4513bc08a7rfc3a5559a.71.16957022303515933" # } # data = self.safe_list(response, 'data', []) return self.parse_orders(data, market, since, limit) def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]: """ https://developer-pro.bitmart.com/en/spot/#account-orders-v4-signed https://developer-pro.bitmart.com/en/futuresv2/#get-order-history-keyed 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 [since]: the earliest time in ms to fetch orders for :param int [limit]: the maximum number of order structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: timestamp in ms of the latest entry :param str [params.marginMode]: *spot only* 'cross' or 'isolated', for margin trading :param str [params.stpMode]: self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both'] :returns Order[]: a list of `order structures ` """ self.load_markets() market = None request: dict = {} if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] type = None type, params = self.handle_market_type_and_params('fetchClosedOrders', market, params) if type != 'spot': if symbol is None: raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument') if since is not None: startTimeKey = 'startTime' if (type == 'spot') else 'start_time' request[startTimeKey] = since endTimeKey = 'endTime' if (type == 'spot') else 'end_time' until = self.safe_integer_2(params, 'until', endTimeKey) if until is not None: params = self.omit(params, ['until']) request[endTimeKey] = until response = None if type == 'spot': marginMode = None marginMode, params = self.handle_margin_mode_and_params('fetchClosedOrders', params) if marginMode == 'isolated': request['orderMode'] = 'iso_margin' response = self.privatePostSpotV4QueryHistoryOrders(self.extend(request, params)) else: response = self.privateGetContractPrivateOrderHistory(self.extend(request, params)) data = self.safe_list(response, 'data', []) return self.parse_orders(data, market, since, limit) def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = 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 [since]: timestamp in ms of the earliest order, default is None :param int [limit]: max number of orders to return, default is None :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a list of `order structures ` """ return self.fetch_orders_by_status('canceled', symbol, since, limit, params) def fetch_order(self, id: str, symbol: Str = None, params={}): """ fetches information on an order made by the user https://developer-pro.bitmart.com/en/spot/#query-order-by-id-v4-signed https://developer-pro.bitmart.com/en/spot/#query-order-by-clientorderid-v4-signed https://developer-pro.bitmart.com/en/futuresv2/#get-order-detail-keyed :param str id: the id of the order :param str symbol: unified symbol of the market the order was made in :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.clientOrderId]: *spot* fetch the order by client order id instead of order id :param str [params.orderType]: *swap only* 'limit', 'market', 'liquidate', 'bankruptcy', 'adl' or 'trailing' :param boolean [params.trailing]: *swap only* set to True if you want to fetch a trailing order :param str [params.stpMode]: self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both'] :returns dict: An `order structure ` """ self.load_markets() request: dict = {} type = None market = None response = None if symbol is not None: market = self.market(symbol) type, params = self.handle_market_type_and_params('fetchOrder', market, params) if type == 'spot': clientOrderId = self.safe_string(params, 'clientOrderId') if not clientOrderId: request['orderId'] = id if clientOrderId is not None: response = self.privatePostSpotV4QueryClientOrder(self.extend(request, params)) else: response = self.privatePostSpotV4QueryOrder(self.extend(request, params)) elif type == 'swap': if symbol is None: raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument') trailing = self.safe_bool(params, 'trailing', False) orderType = self.safe_string(params, 'orderType') params = self.omit(params, ['orderType', 'trailing']) if trailing: orderType = 'trailing' if orderType is not None: request['type'] = orderType request['symbol'] = market['id'] request['order_id'] = id response = self.privateGetContractPrivateOrder(self.extend(request, params)) # # spot # # { # "code": 1000, # "message": "success", # "data": { # "orderId": "183347420821295423", # "clientOrderId": "183347420821295423", # "symbol": "BTC_USDT", # "side": "buy", # "orderMode": "spot", # "type": "limit", # "state": "new", # "price": "24000.00", # "priceAvg": "0.00", # "size": "0.00022", # "filledSize": "0.00000", # "notional": "5.28000000", # "filledNotional": "0.00000000", # "createTime": 1695783014734, # "updateTime": 1695783014762 # }, # "trace": "ce3e6422c8b44d5fag855348a68693ed.63.14957831547451715" # } # # swap # # { # "code": 1000, # "message": "Ok", # "data": { # "order_id": "230927283405028", # "client_order_id": "", # "price": "23000", # "size": "1", # "symbol": "BTCUSDT", # "state": 2, # "side": 1, # "type": "limit", # "leverage": "10", # "open_type": "isolated", # "deal_avg_price": "0", # "deal_size": "0", # "create_time": 1695783433600, # "update_time": 1695783433613 # }, # "trace": "4cad855075664097af6ba5257c47605d.63.14957831547451715" # } # data = self.safe_dict(response, 'data', {}) return self.parse_order(data, market) def fetch_deposit_address(self, code: str, params={}) -> DepositAddress: """ fetch the deposit address for a currency associated with self account https://developer-pro.bitmart.com/en/spot/#deposit-address-keyed :param str code: unified currency code :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `address structure ` """ self.load_markets() currency = self.currency(code) network: Str = None network, params = self.handle_network_code_and_params(params) request: dict = { 'currency': self.get_currency_id_from_code_and_network(code, network), } response = self.privateGetAccountV1DepositAddress(self.extend(request, params)) # # { # "message": "OK", # "code": 1000, # "trace": "0e6edd79-f77f-4251-abe5-83ba75d06c1a", # "data": { # currency: 'ETH', # chain: 'Ethereum', # address: '0x99B5EEc2C520f86F0F62F05820d28D05D36EccCf', # address_memo: '' # } # } # data = self.safe_dict(response, 'data', {}) return self.parse_deposit_address(data, currency) def parse_deposit_address(self, depositAddress, currency=None) -> DepositAddress: # # fetchDepositAddress # { # currency: 'ETH', # chain: 'Ethereum', # address: '0x99B5EEc2C520f86F0F62F05820d28D05D36EccCf', # address_memo: '' # } # # fetchWithdrawAddress # { # "currency": "ETH", # "network": "ETH", # "address": "0x1121", # "memo": "12", # "remark": "12", # "addressType": 0, # "verifyStatus": 0 # } # currencyId = self.safe_string(depositAddress, 'currency') network = self.safe_string_2(depositAddress, 'chain', 'network') if currencyId.find('NFT') < 0: parts = currencyId.split('-') currencyId = self.safe_string(parts, 0) secondPart = self.safe_string(parts, 1) if secondPart is not None: network = secondPart address = self.safe_string(depositAddress, 'address') currency = self.safe_currency(currencyId, currency) self.check_address(address) return { 'info': depositAddress, 'currency': self.safe_string(currency, 'code'), 'network': self.network_id_to_code(network), 'address': address, 'tag': self.safe_string_2(depositAddress, 'address_memo', 'memo'), } def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction: """ make a withdrawal https://developer-pro.bitmart.com/en/spot/#withdraw-signed :param str code: unified currency code :param float amount: the amount to withdraw :param str address: the address to withdraw to :param str tag: :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.network]: the network name for self withdrawal :returns dict: a `transaction structure ` """ tag, params = self.handle_withdraw_tag_and_params(tag, params) self.check_address(address) self.load_markets() currency = self.currency(code) network: Str = None network, params = self.handle_network_code_and_params(params) request: dict = { 'currency': self.get_currency_id_from_code_and_network(code, network), 'amount': amount, 'destination': 'To Digital Address', # To Digital Address, To Binance, To OKEX 'address': address, } if tag is not None: request['address_memo'] = tag response = self.privatePostAccountV1WithdrawApply(self.extend(request, params)) # # { # "code": 1000, # "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1", # "message": "OK", # "data": { # "withdraw_id": "121212" # } # } # data = self.safe_dict(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: Str = None, since: Int = None, limit: Int = None, params={}): self.load_markets() if limit is None: limit = 1000 # max 1000 request: dict = { 'operation_type': type, # deposit or withdraw 'N': limit, } currency = None if code is not None: currency = self.currency(code) request['currency'] = currency['id'] if since is not None: request['startTime'] = since until = self.safe_integer(params, 'until') if until is not None: params = self.omit(params, 'until') request['endTime'] = until 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_dict(response, 'data', {}) records = self.safe_list(data, 'records', []) return self.parse_transactions(records, currency, since, limit) def fetch_deposit(self, id: str, code: Str = None, params={}): """ fetch information on a deposit https://developer-pro.bitmart.com/en/spot/#get-a-deposit-or-withdraw-detail-keyed :param str id: deposit id :param str code: not used by bitmart fetchDeposit() :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `transaction structure ` """ self.load_markets() request: dict = { '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_dict(response, 'data', {}) record = self.safe_dict(data, 'record', {}) return self.parse_transaction(record) def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ fetch all deposits made to an account https://developer-pro.bitmart.com/en/spot/#get-deposit-and-withdraw-history-keyed :param str code: unified currency code :param int [since]: the earliest time in ms to fetch deposits for :param int [limit]: the maximum number of deposits structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `transaction structures ` """ return self.fetch_transactions_by_type('deposit', code, since, limit, params) def fetch_withdrawal(self, id: str, code: Str = None, params={}): """ fetch data on a currency withdrawal via the withdrawal id https://developer-pro.bitmart.com/en/spot/#get-a-deposit-or-withdraw-detail-keyed :param str id: withdrawal id :param str code: not used by bitmart.fetchWithdrawal :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `transaction structure ` """ self.load_markets() request: dict = { 'id': id, } response = self.privateGetAccountV1DepositWithdrawDetail(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_dict(response, 'data', {}) record = self.safe_dict(data, 'record', {}) return self.parse_transaction(record) def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]: """ fetch all withdrawals made from an account https://developer-pro.bitmart.com/en/spot/#get-deposit-and-withdraw-history-keyed :param str code: unified currency code :param int [since]: the earliest time in ms to fetch withdrawals for :param int [limit]: the maximum number of withdrawals structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `transaction structures ` """ return self.fetch_transactions_by_type('withdraw', code, since, limit, params) def parse_transaction_status(self, status: Str): statuses: dict = { '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: dict, currency: Currency = None) -> Transaction: # # 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') networkId: Str = None if currencyId is not None: if currencyId.find('NFT') < 0: parts = currencyId.split('-') currencyId = self.safe_string(parts, 0) networkId = self.safe_string(parts, 1) 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': self.network_id_to_code(networkId), 'address': address, 'addressFrom': None, 'addressTo': None, 'tag': tag, 'tagFrom': None, 'tagTo': None, 'status': status, 'type': type, 'updated': None, 'txid': txid, 'internal': None, 'comment': None, 'timestamp': timestamp if (timestamp != 0) else None, 'datetime': self.iso8601(timestamp) if (timestamp != 0) else None, 'fee': fee, } def repay_isolated_margin(self, symbol: str, code: str, amount, params={}): """ repay borrowed margin and interest https://developer-pro.bitmart.com/en/spot/#margin-repay-isolated-signed :param str symbol: unified market symbol :param str code: unified currency code of the currency to repay :param str amount: the amount to repay :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `margin loan structure ` """ self.load_markets() market = self.market(symbol) currency = self.currency(code) request: dict = { 'symbol': market['id'], 'currency': currency['id'], 'amount': self.currency_to_precision(code, amount), } response = self.privatePostSpotV1MarginIsolatedRepay(self.extend(request, params)) # # { # "message": "OK", # "code": 1000, # "trace": "b0a60b4c-e986-4b54-a190-8f7c05ddf685", # "data": { # "repay_id": "2afcc16d99bd4707818c5a355dc89bed" # } # } # data = self.safe_dict(response, 'data', {}) transaction = self.parse_margin_loan(data, currency) return self.extend(transaction, { 'amount': amount, 'symbol': symbol, }) def borrow_isolated_margin(self, symbol: str, code: str, amount: float, params={}): """ create a loan to borrow margin https://developer-pro.bitmart.com/en/spot/#margin-borrow-isolated-signed :param str symbol: unified market symbol :param str code: unified currency code of the currency to borrow :param str amount: the amount to borrow :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `margin loan structure ` """ self.load_markets() market = self.market(symbol) currency = self.currency(code) request: dict = { 'symbol': market['id'], 'currency': currency['id'], 'amount': self.currency_to_precision(code, amount), } response = self.privatePostSpotV1MarginIsolatedBorrow(self.extend(request, params)) # # { # "message": "OK", # "code": 1000, # "trace": "e6fda683-181e-4e78-ac9c-b27c4c8ba035", # "data": { # "borrow_id": "629a7177a4ed4cf09869c6a4343b788c" # } # } # data = self.safe_dict(response, 'data', {}) transaction = self.parse_margin_loan(data, currency) return self.extend(transaction, { 'amount': amount, 'symbol': symbol, }) def parse_margin_loan(self, info, currency: Currency = None): # # borrowMargin # # { # "borrow_id": "629a7177a4ed4cf09869c6a4343b788c", # } # # repayMargin # # { # "repay_id": "2afcc16d99bd4707818c5a355dc89bed", # } # return { 'id': self.safe_string_2(info, 'borrow_id', 'repay_id'), 'currency': self.safe_currency_code(None, currency), 'amount': None, 'symbol': None, 'timestamp': None, 'datetime': None, 'info': info, } def fetch_isolated_borrow_rate(self, symbol: str, params={}) -> IsolatedBorrowRate: """ fetch the rate of interest to borrow a currency for margin trading https://developer-pro.bitmart.com/en/spot/#get-trading-pair-borrowing-rate-and-amount-keyed :param str symbol: unified symbol of the market to fetch the borrow rate for :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an `isolated borrow rate structure ` """ self.load_markets() market = self.market(symbol) request: dict = { '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_dict(response, 'data', {}) symbols = self.safe_list(data, 'symbols', []) borrowRate = self.safe_dict(symbols, 0, []) return self.parse_isolated_borrow_rate(borrowRate, market) def parse_isolated_borrow_rate(self, info: dict, market: Market = None) -> IsolatedBorrowRate: # # { # "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" # } # } # marketId = self.safe_string(info, 'symbol') symbol = self.safe_symbol(marketId, market) baseData = self.safe_dict(info, 'base', {}) quoteData = self.safe_dict(info, 'quote', {}) baseId = self.safe_string(baseData, 'currency') quoteId = self.safe_string(quoteData, 'currency') return { 'symbol': symbol, 'base': self.safe_currency_code(baseId), 'baseRate': self.safe_number(baseData, 'hourly_interest'), 'quote': self.safe_currency_code(quoteId), 'quoteRate': self.safe_number(quoteData, 'hourly_interest'), 'period': 3600000, # 1-Hour 'timestamp': None, 'datetime': None, 'info': info, } def fetch_isolated_borrow_rates(self, params={}) -> IsolatedBorrowRates: """ fetch the borrow interest rates of all currencies, currently only works for isolated margin https://developer-pro.bitmart.com/en/spot/#get-trading-pair-borrowing-rate-and-amount-keyed :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a list of `isolated borrow rate structures ` """ 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_dict(response, 'data', {}) symbols = self.safe_list(data, 'symbols', []) return self.parse_isolated_borrow_rates(symbols) def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry: """ transfer currency internally between wallets on the same account, currently only supports transfer between spot and margin https://developer-pro.bitmart.com/en/spot/#margin-asset-transfer-signed https://developer-pro.bitmart.com/en/futuresv2/#transfer-signed :param str code: unified currency code :param float amount: amount to transfer :param str fromAccount: account to transfer from :param str toAccount: account to transfer to :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `transfer structure ` """ self.load_markets() currency = self.currency(code) amountToPrecision = self.currency_to_precision(code, amount) request: dict = { 'amount': amountToPrecision, 'currency': currency['id'], } fromId = self.convert_type_to_account(fromAccount) toId = self.convert_type_to_account(toAccount) if fromAccount == 'spot': if toAccount == 'margin': request['side'] = 'in' request['symbol'] = toId elif toAccount == 'swap': request['type'] = 'spot_to_contract' elif toAccount == 'spot': if fromAccount == 'margin': request['side'] = 'out' request['symbol'] = fromId elif fromAccount == 'swap': request['type'] = 'contract_to_spot' else: raise ArgumentsRequired(self.id + ' transfer() requires either fromAccount or toAccount to be spot') response = None if (fromAccount == 'margin') or (toAccount == 'margin'): response = self.privatePostSpotV1MarginIsolatedTransfer(self.extend(request, params)) elif (fromAccount == 'swap') or (toAccount == 'swap'): response = self.privatePostAccountV1TransferContract(self.extend(request, params)) # # margin # # { # "message": "OK", # "code": 1000, # "trace": "b26cecec-ef5a-47d9-9531-2bd3911d3d55", # "data": { # "transfer_id": "ca90d97a621e47d49774f19af6b029f5" # } # } # # swap # # { # "message": "OK", # "code": 1000, # "trace": "4cad858074667097ac6ba5257c57305d.68.16953302431189455", # "data": { # "currency": "USDT", # "amount": "5" # } # } # data = self.safe_dict(response, 'data', {}) return self.extend(self.parse_transfer(data, currency), { 'status': self.parse_transfer_status(self.safe_string_2(response, 'code', 'message')), }) def parse_transfer_status(self, status: Str) -> Str: statuses: dict = { '1000': 'ok', 'OK': 'ok', 'FINISHED': 'ok', } return self.safe_string(statuses, status, status) def parse_transfer_to_account(self, type): types: dict = { 'contract_to_spot': 'spot', 'spot_to_contract': 'swap', } return self.safe_string(types, type, type) def parse_transfer_from_account(self, type): types: dict = { 'contract_to_spot': 'swap', 'spot_to_contract': 'spot', } return self.safe_string(types, type, type) def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry: # # margin # # { # "transfer_id": "ca90d97a621e47d49774f19af6b029f5" # } # # swap # # { # "currency": "USDT", # "amount": "5" # } # # fetchTransfers # # { # "transfer_id": "902463535961567232", # "currency": "USDT", # "amount": "5", # "type": "contract_to_spot", # "state": "FINISHED", # "timestamp": 1695330539565 # } # currencyId = self.safe_string(transfer, 'currency') timestamp = self.safe_integer(transfer, 'timestamp') return { 'id': self.safe_string(transfer, 'transfer_id'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'currency': self.safe_currency_code(currencyId, currency), 'amount': self.safe_number(transfer, 'amount'), 'fromAccount': self.parse_transfer_from_account(self.safe_string(transfer, 'type')), 'toAccount': self.parse_transfer_to_account(self.safe_string(transfer, 'type')), 'status': self.parse_transfer_status(self.safe_string(transfer, 'state')), } def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[TransferEntry]: """ fetch a history of internal transfers made on an account, only transfers between spot and swap are supported https://developer-pro.bitmart.com/en/futuresv2/#get-transfer-list-signed :param str code: unified currency code of the currency transferred :param int [since]: the earliest time in ms to fetch transfers for :param int [limit]: the maximum number of transfer structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.page]: the required number of pages, default is 1, max is 1000 :param int [params.until]: the latest time in ms to fetch transfers for :returns dict[]: a list of `transfer structures ` """ self.load_markets() if limit is None: limit = 10 request: dict = { 'page': self.safe_integer(params, 'page', 1), # default is 1, max is 1000 'limit': limit, # default is 10, max is 100 } currency = None if code is not None: currency = self.currency(code) request['currency'] = currency['id'] if since is not None: request['time_start'] = since if limit is not None: request['limit'] = limit until = self.safe_integer(params, 'until') # unified in milliseconds endTime = self.safe_integer(params, 'time_end', until) # exchange-specific in milliseconds params = self.omit(params, ['until']) if endTime is not None: request['time_end'] = endTime response = self.privatePostAccountV1TransferContractList(self.extend(request, params)) # # { # "message": "OK", # "code": 1000, # "trace": "7f9d93e10f9g4513bc08a7btc2a5559a.69.16953325693032193", # "data": { # "records": [ # { # "transfer_id": "902463535961567232", # "currency": "USDT", # "amount": "5", # "type": "contract_to_spot", # "state": "FINISHED", # "timestamp": 1695330539565 # }, # ] # } # } # data = self.safe_dict(response, 'data', {}) records = self.safe_list(data, 'records', []) return self.parse_transfers(records, currency, since, limit) def fetch_borrow_interest(self, code: Str = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[BorrowInterest]: """ fetch the interest owed by the user for borrowing currency for margin trading https://developer-pro.bitmart.com/en/spot/#get-borrow-record-isolated-keyed :param str code: unified currency code :param str symbol: unified market symbol when fetch interest in isolated markets :param int [since]: the earliest time in ms to fetch borrrow interest for :param int [limit]: the maximum number of structures to retrieve :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `borrow interest structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchBorrowInterest() requires a symbol argument') self.load_markets() market = self.market(symbol) request: dict = { '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_dict(response, 'data', {}) rows = self.safe_list(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: dict, market: Market = None) -> BorrowInterest: # # { # "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 { 'info': info, 'symbol': self.safe_string(market, 'symbol'), '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'), 'marginMode': 'isolated', 'timestamp': timestamp, # borrow creation time 'datetime': self.iso8601(timestamp), } def fetch_open_interest(self, symbol: str, params={}): """ Retrieves the open interest of a currency https://developer-pro.bitmart.com/en/futuresv2/#get-futures-openinterest :param str symbol: Unified CCXT market symbol :param dict [params]: exchange specific parameters :returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure: """ self.load_markets() market = self.market(symbol) if not market['contract']: raise BadRequest(self.id + ' fetchOpenInterest() supports contract markets only') request: dict = { 'symbol': market['id'], } response = self.publicGetContractPublicOpenInterest(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": { # "timestamp": 1694657502415, # "symbol": "BTCUSDT", # "open_interest": "265231.721368593081729069", # "open_interest_value": "7006353.83988919" # }, # "trace": "7f9c94e10f9d4513bc08a7bfc2a5559a.72.16946575108274991" # } # data = self.safe_dict(response, 'data', {}) return self.parse_open_interest(data, market) def parse_open_interest(self, interest, market: Market = None): # # { # "timestamp": 1694657502415, # "symbol": "BTCUSDT", # "open_interest": "265231.721368593081729069", # "open_interest_value": "7006353.83988919" # } # timestamp = self.safe_integer(interest, 'timestamp') id = self.safe_string(interest, 'symbol') return self.safe_open_interest({ 'symbol': self.safe_symbol(id, market), 'openInterestAmount': self.safe_number(interest, 'open_interest'), 'openInterestValue': self.safe_number(interest, 'open_interest_value'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'info': interest, }, market) def set_leverage(self, leverage: int, symbol: Str = None, params={}): """ set the level of leverage for a market https://developer-pro.bitmart.com/en/futuresv2/#submit-leverage-signed :param float leverage: the rate of leverage :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.marginMode]: 'isolated' or 'cross' :returns dict: response from the exchange """ if symbol is None: raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument') marginMode = None marginMode, params = self.handle_margin_mode_and_params('setLeverage', params) self.check_required_argument('setLeverage', marginMode, 'marginMode', ['isolated', 'cross']) self.load_markets() market = self.market(symbol) if not market['swap']: raise BadSymbol(self.id + ' setLeverage() supports swap contracts only') request: dict = { 'symbol': market['id'], 'leverage': str(leverage), 'open_type': marginMode, } return self.privatePostContractPrivateSubmitLeverage(self.extend(request, params)) def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate: """ fetch the current funding rate https://developer-pro.bitmart.com/en/futuresv2/#get-current-funding-rate :param str symbol: unified market symbol :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `funding rate structure ` """ self.load_markets() market = self.market(symbol) if not market['swap']: raise BadSymbol(self.id + ' fetchFundingRate() supports swap contracts only') request: dict = { 'symbol': market['id'], } response = self.publicGetContractPublicFundingRate(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": { # "symbol": "BTCUSDT", # "expected_rate": "-0.0000238", # "rate_value": "0.000009601106", # "funding_time": 1761292800000, # "funding_upper_limit": "0.0375", # "funding_lower_limit": "-0.0375", # "timestamp": 1761291544336 # }, # "trace": "64b7a589-e1e-4ac2-86b1-41058757421" # } # data = self.safe_dict(response, 'data', {}) return self.parse_funding_rate(data, market) def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ fetches historical funding rate prices https://developer-pro.bitmart.com/en/futuresv2/#get-funding-rate-history :param str symbol: unified symbol of the market to fetch the funding rate history for :param int [since]: not sent to exchange api, exchange api always returns the most recent data, only used to filter exchange response :param int [limit]: the maximum amount of funding rate structures to fetch :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `funding rate structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument') self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } if limit is not None: request['limit'] = limit response = self.publicGetContractPublicFundingRateHistory(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": { # "list": [ # { # "symbol": "BTCUSDT", # "funding_rate": "0.000091412174", # "funding_time": "1734336000000" # }, # ] # }, # "trace": "fg73d949fgfdf6a40c8fc7f5ae6738.54.345345345345" # } # data = self.safe_dict(response, 'data', {}) result = self.safe_list(data, 'list', []) rates = [] for i in range(0, len(result)): entry = result[i] marketId = self.safe_string(entry, 'symbol') symbolInner = self.safe_symbol(marketId, market, '-', 'swap') timestamp = self.safe_integer(entry, 'funding_time') rates.append({ 'info': entry, 'symbol': symbolInner, 'fundingRate': self.safe_number(entry, 'funding_rate'), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), }) sorted = self.sort_by(rates, 'timestamp') return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit) def parse_funding_rate(self, contract, market: Market = None) -> FundingRate: # # { # "symbol": "BTCUSDT", # "expected_rate": "-0.0000238", # "rate_value": "0.000009601106", # "funding_time": 1761292800000, # "funding_upper_limit": "0.0375", # "funding_lower_limit": "-0.0375", # "timestamp": 1761291544336 # } # marketId = self.safe_string(contract, 'symbol') timestamp = self.safe_integer(contract, 'timestamp') fundingTimestamp = self.safe_integer(contract, 'funding_time') return { 'info': contract, 'symbol': self.safe_symbol(marketId, market), 'markPrice': None, 'indexPrice': None, 'interestRate': None, 'estimatedSettlePrice': None, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'fundingRate': self.safe_number(contract, 'expected_rate'), 'fundingTimestamp': fundingTimestamp, 'fundingDatetime': self.iso8601(fundingTimestamp), 'nextFundingRate': None, 'nextFundingTimestamp': None, 'nextFundingDatetime': None, 'previousFundingRate': self.safe_number(contract, 'rate_value'), 'previousFundingTimestamp': None, 'previousFundingDatetime': None, 'interval': None, } def fetch_position(self, symbol: str, params={}): """ fetch data on a single open contract trade position https://developer-pro.bitmart.com/en/futuresv2/#get-current-position-keyed :param str symbol: unified market symbol of the market the position is held in :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: a `position structure ` """ self.load_markets() market = self.market(symbol) request: dict = { 'symbol': market['id'], } response = self.privateGetContractPrivatePosition(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": [ # { # "symbol": "BTCUSDT", # "leverage": "10", # "timestamp": 1696392515269, # "current_fee": "0.0014250028", # "open_timestamp": 1696392256998, # "current_value": "27.4039", # "mark_price": "27.4039", # "position_value": "27.4079", # "position_cross": "3.75723474", # "maintenance_margin": "0.1370395", # "close_vol": "0", # "close_avg_price": "0", # "open_avg_price": "27407.9", # "entry_price": "27407.9", # "current_amount": "1", # "unrealized_value": "-0.004", # "realized_value": "-0.01644474", # "position_type": 1 # } # ], # "trace":"4cad855074664097ac5ba5257c47305d.67.16963925142065945" # } # data = self.safe_list(response, 'data', []) first = self.safe_dict(data, 0, {}) return self.parse_position(first, market) def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]: """ fetch all open contract positions https://developer-pro.bitmart.com/en/futuresv2/#get-current-position-keyed https://developer-pro.bitmart.com/en/futuresv2/#get-current-position-v2-keyed :param str[]|None symbols: list of unified market symbols :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict[]: a list of `position structures ` """ self.load_markets() market = None symbolsLength = None if symbols is not None: symbolsLength = len(symbols) first = self.safe_string(symbols, 0) market = self.market(first) request: dict = {} if symbolsLength == 1: # only supports symbols or sending one symbol request['symbol'] = market['id'] response = self.privateGetContractPrivatePositionV2(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": [ # { # "symbol": "BTCUSDT", # "leverage": "10", # "timestamp": 1696392515269, # "current_fee": "0.0014250028", # "open_timestamp": 1696392256998, # "current_value": "27.4039", # "mark_price": "27.4039", # "position_value": "27.4079", # "position_cross": "3.75723474", # "maintenance_margin": "0.1370395", # "close_vol": "0", # "close_avg_price": "0", # "open_avg_price": "27407.9", # "entry_price": "27407.9", # "current_amount": "1", # "unrealized_value": "-0.004", # "realized_value": "-0.01644474", # "position_type": 1 # }, # ], # "trace":"4cad855074664097ac5ba5257c47305d.67.16963925142065945" # } # positions = self.safe_list(response, 'data', []) result = [] for i in range(0, len(positions)): result.append(self.parse_position(positions[i])) symbols = self.market_symbols(symbols) return self.filter_by_array_positions(result, 'symbol', symbols, False) def parse_position(self, position: dict, market: Market = None): # # { # "symbol": "BTCUSDT", # "leverage": "10", # "timestamp": 1696392515269, # "current_fee": "0.0014250028", # "open_timestamp": 1696392256998, # "current_value": "27.4039", # "mark_price": "27.4039", # "position_value": "27.4079", # "position_cross": "3.75723474", # "maintenance_margin": "0.1370395", # "close_vol": "0", # "close_avg_price": "0", # "open_avg_price": "27407.9", # "entry_price": "27407.9", # "current_amount": "1", # "unrealized_value": "-0.004", # "realized_value": "-0.01644474", # "position_type": 1 # } # marketId = self.safe_string(position, 'symbol') market = self.safe_market(marketId, market) symbol = market['symbol'] timestamp = self.safe_integer(position, 'timestamp') side = self.safe_integer(position, 'position_type') maintenanceMargin = self.safe_string(position, 'maintenance_margin') notional = self.safe_string(position, 'current_value') collateral = self.safe_string(position, 'position_cross') maintenanceMarginPercentage = Precise.string_div(maintenanceMargin, notional) marginRatio = Precise.string_div(maintenanceMargin, collateral) return self.safe_position({ 'info': position, 'id': None, 'symbol': symbol, 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'lastUpdateTimestamp': None, 'hedged': None, 'side': 'long' if (side == 1) else 'short', 'contracts': self.safe_number(position, 'current_amount'), 'contractSize': self.safe_number(market, 'contractSize'), 'entryPrice': self.safe_number(position, 'entry_price'), 'markPrice': self.safe_number(position, 'mark_price'), 'lastPrice': None, 'notional': self.parse_number(notional), 'leverage': self.safe_number(position, 'leverage'), 'collateral': self.parse_number(collateral), 'initialMargin': None, 'initialMarginPercentage': None, 'maintenanceMargin': self.parse_number(maintenanceMargin), 'maintenanceMarginPercentage': self.parse_number(maintenanceMarginPercentage), 'unrealizedPnl': self.safe_number(position, 'unrealized_value'), 'realizedPnl': self.safe_number(position, 'realized_value'), 'liquidationPrice': None, 'marginMode': None, 'percentage': None, 'marginRatio': self.parse_number(marginRatio), 'stopLossPrice': None, 'takeProfitPrice': None, }) def fetch_my_liquidations(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}): """ retrieves the users liquidated positions https://developer-pro.bitmart.com/en/futuresv2/#get-order-history-keyed :param str symbol: unified CCXT market symbol :param int [since]: the earliest time in ms to fetch liquidations for :param int [limit]: the maximum number of liquidation structures to retrieve :param dict [params]: exchange specific parameters for the bitmart api endpoint :param int [params.until]: timestamp in ms of the latest liquidation :returns dict: an array of `liquidation structures ` """ if symbol is None: raise ArgumentsRequired(self.id + ' fetchMyLiquidations() requires a symbol argument') self.load_markets() market = self.market(symbol) if not market['swap']: raise NotSupported(self.id + ' fetchMyLiquidations() supports swap markets only') request: dict = { 'symbol': market['id'], } if since is not None: request['start_time'] = since request, params = self.handle_until_option('end_time', request, params) response = self.privateGetContractPrivateOrderHistory(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": [ # { # "order_id": "231007865458273", # "client_order_id": "", # "price": "27407.9", # "size": "1", # "symbol": "BTCUSDT", # "state": 4, # "side": 3, # "type": "liquidate", # "leverage": "10", # "open_type": "isolated", # "deal_avg_price": "27422.6", # "deal_size": "1", # "create_time": 1696405864011, # "update_time": 1696405864045 # }, # ], # "trace": "4cad855074664097ac6ba4257c47305d.71.16965658195443021" # } # data = self.safe_list(response, 'data', []) result = [] for i in range(0, len(data)): entry = data[i] checkLiquidation = self.safe_string(entry, 'type') if checkLiquidation == 'liquidate': result.append(entry) return self.parse_liquidations(result, market, since, limit) def parse_liquidation(self, liquidation, market: Market = None): # # { # "order_id": "231007865458273", # "client_order_id": "", # "price": "27407.9", # "size": "1", # "symbol": "BTCUSDT", # "state": 4, # "side": 3, # "type": "market", # "leverage": "10", # "open_type": "isolated", # "deal_avg_price": "27422.6", # "deal_size": "1", # "create_time": 1696405864011, # "update_time": 1696405864045 # } # marketId = self.safe_string(liquidation, 'symbol') timestamp = self.safe_integer(liquidation, 'update_time') contractsString = self.safe_string(liquidation, 'deal_size') contractSizeString = self.safe_string(market, 'contractSize') priceString = self.safe_string(liquidation, 'deal_avg_price') baseValueString = Precise.string_mul(contractsString, contractSizeString) quoteValueString = Precise.string_mul(baseValueString, priceString) return self.safe_liquidation({ 'info': liquidation, 'symbol': self.safe_symbol(marketId, market), 'contracts': self.parse_number(contractsString), 'contractSize': self.parse_number(contractSizeString), 'price': self.parse_number(priceString), 'baseValue': self.parse_number(baseValueString), 'quoteValue': self.parse_number(quoteValueString), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), }) def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order: """ edits an open order https://developer-pro.bitmart.com/en/futuresv2/#modify-plan-order-signed https://developer-pro.bitmart.com/en/futuresv2/#modify-tp-sl-order-signed https://developer-pro.bitmart.com/en/futuresv2/#modify-preset-plan-order-signed https://developer-pro.bitmart.com/en/futuresv2/#modify-limit-order-signed :param str id: order id :param str symbol: unified symbol of the market to edit an order in :param str type: 'market' or 'limit' :param str side: 'buy' or 'sell' :param float [amount]: how much you want to trade in units of the base currency :param float [price]: the price to fulfill the order, in units of the quote currency, ignored in market orders :param dict [params]: extra parameters specific to the exchange API endpoint :param str [params.triggerPrice]: *swap only* the price to trigger a stop order :param str [params.stopLossPrice]: *swap only* the price to trigger a stop-loss order :param str [params.takeProfitPrice]: *swap only* the price to trigger a take-profit order :param str [params.stopLoss.triggerPrice]: *swap only* the price to trigger a preset stop-loss order :param str [params.takeProfit.triggerPrice]: *swap only* the price to trigger a preset take-profit order :param str [params.clientOrderId]: client order id of the order :param int [params.price_type]: *swap only* 1: last price, 2: fair price, default is 1 :param int [params.plan_category]: *swap tp/sl only* 1: tp/sl, 2: position tp/sl, default is 1 :returns dict: an `order structure ` """ self.load_markets() market = self.market(symbol) if not market['swap']: raise NotSupported(self.id + ' editOrder() does not support ' + market['type'] + ' markets, only swap markets are supported') stopLossPrice = self.safe_string(params, 'stopLossPrice') takeProfitPrice = self.safe_string(params, 'takeProfitPrice') triggerPrice = self.safe_string_n(params, ['triggerPrice', 'stopPrice', 'trigger_price']) stopLoss = self.safe_dict(params, 'stopLoss', {}) takeProfit = self.safe_dict(params, 'takeProfit', {}) presetStopLoss = self.safe_string(stopLoss, 'triggerPrice') presetTakeProfit = self.safe_string(takeProfit, 'triggerPrice') isTriggerOrder = triggerPrice is not None isStopLoss = stopLossPrice is not None isTakeProfit = takeProfitPrice is not None isPresetStopLoss = presetStopLoss is not None isPresetTakeProfit = presetTakeProfit is not None isLimitOrder = (type == 'limit') request: dict = { 'symbol': market['id'], } clientOrderId = self.safe_string(params, 'clientOrderId') if clientOrderId is not None: params = self.omit(params, 'clientOrderId') request['client_order_id'] = clientOrderId if id is not None: request['order_id'] = id params = self.omit(params, ['triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'stopLoss', 'takeProfit']) response = None if isTriggerOrder or isStopLoss or isTakeProfit: request['price_type'] = self.safe_integer(params, 'price_type', 1) if price is not None: request['executive_price'] = self.price_to_precision(symbol, price) if isTriggerOrder: request['type'] = type request['trigger_price'] = self.price_to_precision(symbol, triggerPrice) response = self.privatePostContractPrivateModifyPlanOrder(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": { # "order_id": "3000023150003503" # }, # "trace": "324523453245.108.1734567125596324575" # } # elif isStopLoss or isTakeProfit: request['category'] = type if isStopLoss: request['trigger_price'] = self.price_to_precision(symbol, stopLossPrice) else: request['trigger_price'] = self.price_to_precision(symbol, takeProfitPrice) response = self.privatePostContractPrivateModifyTpSlOrder(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": { # "order_id": "3000023150003480" # }, # "trace": "23452345.104.1724536582682345459" # } # elif isPresetStopLoss or isPresetTakeProfit: if isPresetStopLoss: request['preset_stop_loss_price_type'] = self.safe_integer(params, 'price_type', 1) request['preset_stop_loss_price'] = self.price_to_precision(symbol, presetStopLoss) else: request['preset_take_profit_price_type'] = self.safe_integer(params, 'price_type', 1) request['preset_take_profit_price'] = self.price_to_precision(symbol, presetTakeProfit) response = self.privatePostContractPrivateModifyPresetPlanOrder(self.extend(request, params)) # # { # "code": 1000, # "message": "Ok", # "data": { # "order_id": "3000023150003496" # }, # "trace": "a5c3234534534a836bc476a203.123452.172716624359200197" # } # elif isLimitOrder: request['order_id'] = self.parse_to_int(id) # reparse id self endpoint is the only one requiring it if amount is not None: request['size'] = self.amount_to_precision(symbol, amount) if price is not None: request['price'] = self.price_to_precision(symbol, price) response = self.privatePostContractPrivateModifyLimitOrder(self.extend(request, params)) else: raise NotSupported(self.id + ' editOrder() only supports limit, trigger, stop loss and take profit orders') data = self.safe_dict(response, 'data', {}) return self.parse_order(data, market) def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]: """ fetch the history of changes, actions done by the user or operations that altered the balance of the user https://developer-pro.bitmart.com/en/futuresv2/#get-transaction-history-keyed :param str [code]: unified currency code :param int [since]: timestamp in ms of the earliest ledger entry :param int [limit]: max number of ledger entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: timestamp in ms of the latest ledger entry :returns dict[]: a list of `ledger structures ` """ self.load_markets() currency = None if code is not None: currency = self.currency(code) request: dict = {} request, params = self.handle_until_option('end_time', request, params) transactionsRequest = self.fetch_transactions_request(0, None, since, limit, params) response = self.privateGetContractPrivateTransactionHistory(transactionsRequest) # # { # "code": 1000, # "message": "Ok", # "data": [ # { # "time": "1734422402121", # "type": "Funding Fee", # "amount": "-0.00008253", # "asset": "USDT", # "symbol": "LTCUSDT", # "tran_id": "1734422402121", # "flow_type": 3 # }, # ], # "trace": "4cd11f83c71egfhfgh842790f07241e.23.173442343427772866" # } # data = self.safe_list(response, 'data', []) return self.parse_ledger(data, currency, since, limit) def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry: # # { # "time": "1734422402121", # "type": "Funding Fee", # "amount": "-0.00008253", # "asset": "USDT", # "symbol": "LTCUSDT", # "tran_id": "1734422402121", # "flow_type": 3 # } # amount = self.safe_string(item, 'amount') direction = None if Precise.string_le(amount, '0'): direction = 'out' amount = Precise.string_mul('-1', amount) else: direction = 'in' currencyId = self.safe_string(item, 'asset') currency = self.safe_currency(currencyId, currency) timestamp = self.safe_integer(item, 'time') type = self.safe_string(item, 'type') return self.safe_ledger_entry({ 'info': item, 'id': self.safe_string(item, 'tran_id'), 'direction': direction, 'account': None, 'referenceAccount': None, 'referenceId': self.safe_string(item, 'tradeId'), 'type': self.parse_ledger_entry_type(type), 'currency': currency['code'], 'amount': self.parse_number(amount), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'before': None, 'after': None, 'status': None, 'fee': None, }, currency) def parse_ledger_entry_type(self, type): ledgerType: dict = { 'Commission Fee': 'fee', 'Funding Fee': 'fee', 'Realized PNL': 'trade', 'Transfer': 'transfer', 'Liquidation Clearance': 'settlement', } return self.safe_string(ledgerType, type, type) def fetch_transactions_request(self, flowType: Int = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}): request: dict = {} if flowType is not None: request['flow_type'] = flowType market = None if symbol is not None: market = self.market(symbol) request['symbol'] = market['id'] if since is not None: request['start_time'] = since if limit is not None: request['page_size'] = limit request, params = self.handle_until_option('end_time', request, params) return self.extend(request, params) def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[FundingHistory]: """ fetch the history of funding payments paid and received on self account https://developer-pro.bitmart.com/en/futuresv2/#get-transaction-history-keyed :param str [symbol]: unified market symbol :param int [since]: the starting timestamp in milliseconds :param int [limit]: the number of entries to return :param dict [params]: extra parameters specific to the exchange API endpoint :param int [params.until]: the latest time in ms to fetch funding history for :returns dict[]: a list of `funding history structures ` """ self.load_markets() market = None if symbol is not None: market = self.market(symbol) request: dict = {} request, params = self.handle_until_option('end_time', request, params) transactionsRequest = self.fetch_transactions_request(3, symbol, since, limit, params) response = self.privateGetContractPrivateTransactionHistory(transactionsRequest) # # { # "code": 1000, # "message": "Ok", # "data": [ # { # "time": "1734422402121", # "type": "Funding Fee", # "amount": "-0.00008253", # "asset": "USDT", # "symbol": "LTCUSDT", # "tran_id": "1734422402121", # "flow_type": 3 # }, # ], # "trace": "4cd11f83c71egfhfgh842790f07241e.23.173442343427772866" # } # data = self.safe_list(response, 'data', []) return self.parse_funding_histories(data, market, since, limit) def parse_funding_history(self, contract, market: Market = None): # # { # "time": "1734422402121", # "type": "Funding Fee", # "amount": "-0.00008253", # "asset": "USDT", # "symbol": "LTCUSDT", # "tran_id": "1734422402121", # "flow_type": 3 # } # marketId = self.safe_string(contract, 'symbol') currencyId = self.safe_string(contract, 'asset') timestamp = self.safe_integer(contract, 'time') return { 'info': contract, 'symbol': self.safe_symbol(marketId, market, None, 'swap'), 'code': self.safe_currency_code(currencyId), 'timestamp': timestamp, 'datetime': self.iso8601(timestamp), 'id': self.safe_string(contract, 'tran_id'), 'amount': self.safe_number(contract, 'amount'), } def parse_funding_histories(self, contracts, market=None, since: Int = None, limit: Int = None) -> List[FundingHistory]: result = [] for i in range(0, len(contracts)): contract = contracts[i] result.append(self.parse_funding_history(contract, market)) sorted = self.sort_by(result, 'timestamp') return self.filter_by_since_limit(sorted, since, limit) def fetch_withdraw_addresses(self, code: str, note=None, networkCode=None, params={}): self.load_markets() codes = None if code is not None: currency = self.currency(code) code = currency['code'] codes = [code] response = self.privateGetAccountV1WithdrawAddressList(params) # # { # "message": "OK", # "code": 1000, # "trace": "0e6edd79-f77f-4251-abe5-83ba75d06c1a", # "data": { # "list": [ # { # "currency": "ETH", # "network": "ETH", # "address": "0x1121", # "memo": "12", # "remark": "12", # "addressType": 0, # "verifyStatus": 0 # } # ] # } # } # data = self.safe_dict(response, 'data', {}) list = self.safe_list(data, 'list', []) allAddresses = self.parse_deposit_addresses(list, codes, False) addresses = [] for i in range(0, len(allAddresses)): address = allAddresses[i] noteMatch = (note is None) or (address['note'] == note) networkMatch = (networkCode is None) or (address['network'] == networkCode) if noteMatch and networkMatch: addresses.append(address) return addresses def set_position_mode(self, hedged: bool, symbol: Str = None, params={}): """ set hedged to True or False for a market https://developer-pro.bitmart.com/en/futuresv2/#submit-leverage-signed :param bool hedged: set to True to use dualSidePosition :param str symbol: not used by bingx setPositionMode() :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: response from the exchange """ self.load_markets() positionMode = None if hedged: positionMode = 'hedge_mode' else: positionMode = 'one_way_mode' request: dict = { 'position_mode': positionMode, } # # { # "code": 1000, # "trace": "0cc6f4c4-8b8c-4253-8e90-8d3195aa109c", # "message": "Ok", # "data": { # "position_mode":"one_way_mode" # } # } # return self.privatePostContractPrivateSetPositionMode(self.extend(request, params)) def fetch_position_mode(self, symbol: Str = None, params={}): """ fetchs the position mode, hedged or one way, hedged for binance is set identically for all linear markets or all inverse markets https://developer-pro.bitmart.com/en/futuresv2/#get-position-mode-keyed :param str symbol: not used :param dict [params]: extra parameters specific to the exchange API endpoint :returns dict: an object detailing whether the market is in hedged or one-way mode """ response = self.privateGetContractPrivateGetPositionMode(params) # # { # "code": 1000, # "trace": "0cc6f4c4-8b8c-4253-8e90-8d3195aa109c", # "message": "Ok", # "data": { # "position_mode":"one_way_mode" # } # } # data = self.safe_dict(response, 'data') positionMode = self.safe_string(data, 'position_mode') return { 'info': response, 'hedged': (positionMode == 'hedge_mode'), } def nonce(self): return self.milliseconds() - self.options['timeDifference'] def sign(self, path, api='public', method='GET', params={}, headers=None, body=None): parts = path.split('/') # to do: refactor api endpoints with spot/swap sections category = self.safe_string(parts, 0, 'spot') market = 'spot' if (category == 'spot' or category == 'account') else 'swap' baseUrl = self.implode_hostname(self.urls['api'][market]) 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.nonce()) brokerId = self.safe_string(self.options, 'brokerId', 'CCXTxBitmart000') headers = { 'X-BM-KEY': self.apiKey, 'X-BM-TIMESTAMP': timestamp, 'X-BM-BROKER-ID': brokerId, '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), hashlib.sha256) headers['X-BM-SIGN'] = signature return {'url': url, 'method': method, 'body': body, 'headers': headers} def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody): if response is None: return None # # 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":{}} # {"code":40012,"message":"You contract account available balance not enough.","trace":"..."} # # contract # # {"errno":"OK","message":"INVALID_PARAMETER","code":49998,"trace":"eb5ebb54-23cd-4de2-9064-e090b6c3b2e3","data":null} # message = self.safe_string_lower(response, 'message') isErrorMessage = (message is not None) and (message != 'ok') and (message != 'success') errorCode = self.safe_string(response, 'code') isErrorCode = (errorCode is not None) and (errorCode != '1000') if isErrorCode or isErrorMessage: feedback = self.id + ' ' + body self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback) self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback) self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback) self.throw_broadly_matched_exception(self.exceptions['broad'], errorCode, feedback) raise ExchangeError(feedback) # unknown message return None