Files
ccxt_with_mt5/ccxt/bitmart.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

5361 lines
244 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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 positions margin shouldnt 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 <https://docs.ccxt.com/#/?id=exchange-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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-book-structure>` 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 <https://docs.ccxt.com/#/?id=public-trades>`
"""
self.load_markets()
market = self.market(symbol)
if not market['spot']:
raise NotSupported(self.id + ' fetchTrades() does not support ' + market['type'] + ' orders, only spot orders are accepted')
request: 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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=trade-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=order-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
tag, params = self.handle_withdraw_tag_and_params(tag, params)
self.check_address(address)
self.load_markets()
currency = self.currency(code)
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=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 <https://github.com/ccxt/ccxt/wiki/Manual#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 <https://docs.ccxt.com/#/?id=isolated-borrow-rate-structure>`
"""
self.load_markets()
response = self.privateGetSpotV1MarginIsolatedPairs(params)
#
# {
# "message": "OK",
# "code": 1000,
# "trace": "0985a130-a5ae-4fc1-863f-4704e214f585",
# "data": {
# "symbols": [
# {
# "symbol": "BTC_USDT",
# "max_leverage": "5",
# "symbol_enabled": True,
# "base": {
# "currency": "BTC",
# "daily_interest": "0.00055000",
# "hourly_interest": "0.00002291",
# "max_borrow_amount": "2.00000000",
# "min_borrow_amount": "0.00000001",
# "borrowable_amount": "0.00670810"
# },
# "quote": {
# "currency": "USDT",
# "daily_interest": "0.00055000",
# "hourly_interest": "0.00002291",
# "max_borrow_amount": "50000.00000000",
# "min_borrow_amount": "0.00000001",
# "borrowable_amount": "135.12575038"
# }
# }
# ]
# }
# }
#
data = self.safe_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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=transfer-structure>`
"""
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 <https://docs.ccxt.com/#/?id=borrow-interest-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=position-structure>`
"""
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 <https://docs.ccxt.com/#/?id=liquidation-structure>`
"""
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 <https://docs.ccxt.com/#/?id=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 <https://docs.ccxt.com/#/?id=ledger>`
"""
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 <https://docs.ccxt.com/#/?id=funding-history-structure>`
"""
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