6418 lines
274 KiB
Python
6418 lines
274 KiB
Python
# -*- 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.bingx import ImplicitAPI
|
|
import hashlib
|
|
import numbers
|
|
from ccxt.base.types import Any, Balances, Currencies, Currency, DepositAddress, Int, Leverage, MarginMode, MarginModification, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, 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 InvalidOrder
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import OperationFailed
|
|
from ccxt.base.errors import DDoSProtection
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class bingx(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(bingx, self).describe(), {
|
|
'id': 'bingx',
|
|
'name': 'BingX',
|
|
'countries': ['US'], # North America, Canada, the EU, Hong Kong and Taiwan
|
|
'rateLimit': 100,
|
|
'version': 'v1',
|
|
'certified': True,
|
|
'pro': True,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': True,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'borrowMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelAllOrdersAfter': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': True,
|
|
'closeAllPositions': True,
|
|
'closePosition': True,
|
|
'createMarketBuyOrderWithCost': True,
|
|
'createMarketOrderWithCost': True,
|
|
'createMarketSellOrderWithCost': True,
|
|
'createOrder': True,
|
|
'createOrders': True,
|
|
'createOrderWithTakeProfitAndStopLoss': True,
|
|
'createReduceOnlyOrder': True,
|
|
'createStopLossOrder': True,
|
|
'createStopOrder': True,
|
|
'createTakeProfitOrder': True,
|
|
'createTrailingAmountOrder': True,
|
|
'createTrailingPercentOrder': True,
|
|
'createTriggerOrder': True,
|
|
'editOrder': True,
|
|
'fetchAllGreeks': False,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchCanceledOrders': True,
|
|
'fetchClosedOrders': True,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDepositAddress': True,
|
|
'fetchDepositAddresses': False,
|
|
'fetchDepositAddressesByNetwork': True,
|
|
'fetchDeposits': True,
|
|
'fetchDepositWithdrawFee': 'emulated',
|
|
'fetchDepositWithdrawFees': True,
|
|
'fetchFundingRate': True,
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': True,
|
|
'fetchGreeks': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchLeverage': True,
|
|
'fetchLiquidations': False,
|
|
'fetchMarginAdjustmentHistory': False,
|
|
'fetchMarginMode': True,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': True,
|
|
'fetchMarkPrice': True,
|
|
'fetchMarkPrices': True,
|
|
'fetchMyLiquidations': True,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': True,
|
|
'fetchOpenOrders': True,
|
|
'fetchOption': False,
|
|
'fetchOptionChain': False,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': True,
|
|
'fetchPosition': True,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': True,
|
|
'fetchPositions': True,
|
|
'fetchPositionsHistory': True,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': True,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': True,
|
|
'fetchTransfers': True,
|
|
'fetchVolatilityHistory': False,
|
|
'fetchWithdrawals': True,
|
|
'reduceMargin': True,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'sandbox': True,
|
|
'setLeverage': True,
|
|
'setMargin': True,
|
|
'setMarginMode': True,
|
|
'setPositionMode': True,
|
|
'transfer': True,
|
|
},
|
|
'hostname': 'bingx.com',
|
|
'urls': {
|
|
'logo': 'https://github-production-user-asset-6210df.s3.amazonaws.com/1294454/253675376-6983b72e-4999-4549-b177-33b374c195e3.jpg',
|
|
'api': {
|
|
'fund': 'https://open-api.{hostname}/openApi',
|
|
'spot': 'https://open-api.{hostname}/openApi',
|
|
'swap': 'https://open-api.{hostname}/openApi',
|
|
'contract': 'https://open-api.{hostname}/openApi',
|
|
'wallets': 'https://open-api.{hostname}/openApi',
|
|
'user': 'https://open-api.{hostname}/openApi',
|
|
'subAccount': 'https://open-api.{hostname}/openApi',
|
|
'account': 'https://open-api.{hostname}/openApi',
|
|
'copyTrading': 'https://open-api.{hostname}/openApi',
|
|
'cswap': 'https://open-api.{hostname}/openApi',
|
|
'api': 'https://open-api.{hostname}/openApi',
|
|
},
|
|
'test': {
|
|
'swap': 'https://open-api-vst.{hostname}/openApi', # only swap is really "test" but since the API keys are the same, we want to keep all the functionalities when the user enables the sandboxmode
|
|
},
|
|
'www': 'https://bingx.com/',
|
|
'doc': 'https://bingx-api.github.io/docs/',
|
|
'referral': 'https://bingx.com/invite/OHETOM',
|
|
},
|
|
'fees': {
|
|
'tierBased': True,
|
|
'spot': {
|
|
'feeSide': 'get',
|
|
'maker': self.parse_number('0.001'),
|
|
'taker': self.parse_number('0.001'),
|
|
},
|
|
'swap': {
|
|
'feeSide': 'quote',
|
|
'maker': self.parse_number('0.0002'),
|
|
'taker': self.parse_number('0.0005'),
|
|
},
|
|
},
|
|
'requiredCredentials': {
|
|
'apiKey': True,
|
|
'secret': True,
|
|
},
|
|
'api': {
|
|
'fund': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'account/balance': 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'spot': {
|
|
'v1': {
|
|
'public': {
|
|
'get': {
|
|
'server/time': 1,
|
|
'common/symbols': 1,
|
|
'market/trades': 1,
|
|
'market/depth': 1,
|
|
'market/kline': 1,
|
|
'ticker/24hr': 1,
|
|
'ticker/price': 1,
|
|
'ticker/bookTicker': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'trade/query': 1,
|
|
'trade/openOrders': 1,
|
|
'trade/historyOrders': 1,
|
|
'trade/myTrades': 2,
|
|
'user/commissionRate': 5,
|
|
'account/balance': 2,
|
|
'oco/orderList': 5,
|
|
'oco/openOrderList': 5,
|
|
'oco/historyOrderList': 5,
|
|
},
|
|
'post': {
|
|
'trade/order': 2,
|
|
'trade/cancel': 2,
|
|
'trade/batchOrders': 5,
|
|
'trade/order/cancelReplace': 5,
|
|
'trade/cancelOrders': 5,
|
|
'trade/cancelOpenOrders': 5,
|
|
'trade/cancelAllAfter': 5,
|
|
'oco/order': 5,
|
|
'oco/cancel': 5,
|
|
},
|
|
},
|
|
},
|
|
'v2': {
|
|
'public': {
|
|
'get': {
|
|
'market/depth': 1,
|
|
'market/kline': 1,
|
|
},
|
|
},
|
|
},
|
|
'v3': {
|
|
'private': {
|
|
'get': {
|
|
'get/asset/transfer': 1,
|
|
'asset/transfer': 1,
|
|
'capital/deposit/hisrec': 1,
|
|
'capital/withdraw/history': 1,
|
|
},
|
|
'post': {
|
|
'post/asset/transfer': 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'swap': {
|
|
'v1': {
|
|
'public': {
|
|
'get': {
|
|
'ticker/price': 1,
|
|
'market/historicalTrades': 1,
|
|
'market/markPriceKlines': 1,
|
|
'trade/multiAssetsRules': 1,
|
|
'tradingRules': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'positionSide/dual': 5,
|
|
'trade/batchCancelReplace': 5,
|
|
'trade/fullOrder': 2,
|
|
'maintMarginRatio': 2,
|
|
'trade/positionHistory': 2,
|
|
'positionMargin/history': 2,
|
|
'twap/openOrders': 5,
|
|
'twap/historyOrders': 5,
|
|
'twap/orderDetail': 5,
|
|
'trade/assetMode': 5,
|
|
'user/marginAssets': 5,
|
|
},
|
|
'post': {
|
|
'trade/cancelReplace': 2,
|
|
'positionSide/dual': 5,
|
|
'trade/batchCancelReplace': 5,
|
|
'trade/closePosition': 2,
|
|
'trade/getVst': 5,
|
|
'twap/order': 5,
|
|
'twap/cancelOrder': 5,
|
|
'trade/assetMode': 5,
|
|
'trade/reverse': 5,
|
|
'trade/autoAddMargin': 5,
|
|
},
|
|
},
|
|
},
|
|
'v2': {
|
|
'public': {
|
|
'get': {
|
|
'server/time': 1,
|
|
'quote/contracts': 1,
|
|
'quote/price': 1,
|
|
'quote/depth': 1,
|
|
'quote/trades': 1,
|
|
'quote/premiumIndex': 1,
|
|
'quote/fundingRate': 1,
|
|
'quote/klines': 1,
|
|
'quote/openInterest': 1,
|
|
'quote/ticker': 1,
|
|
'quote/bookTicker': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'user/balance': 2,
|
|
'user/positions': 2,
|
|
'user/income': 2,
|
|
'trade/openOrders': 2,
|
|
'trade/openOrder': 2,
|
|
'trade/order': 2,
|
|
'trade/marginType': 5,
|
|
'trade/leverage': 2,
|
|
'trade/forceOrders': 1,
|
|
'trade/allOrders': 2,
|
|
'trade/allFillOrders': 2,
|
|
'trade/fillHistory': 2,
|
|
'user/income/export': 2,
|
|
'user/commissionRate': 2,
|
|
'quote/bookTicker': 1,
|
|
},
|
|
'post': {
|
|
'trade/order': 2,
|
|
'trade/batchOrders': 2,
|
|
'trade/closeAllPositions': 2,
|
|
'trade/cancelAllAfter': 5,
|
|
'trade/marginType': 5,
|
|
'trade/leverage': 5,
|
|
'trade/positionMargin': 5,
|
|
'trade/order/test': 2,
|
|
},
|
|
'delete': {
|
|
'trade/order': 2,
|
|
'trade/batchOrders': 2,
|
|
'trade/allOpenOrders': 2,
|
|
},
|
|
},
|
|
},
|
|
'v3': {
|
|
'public': {
|
|
'get': {
|
|
'quote/klines': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'user/balance': 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'cswap': {
|
|
'v1': {
|
|
'public': {
|
|
'get': {
|
|
'market/contracts': 1,
|
|
'market/premiumIndex': 1,
|
|
'market/openInterest': 1,
|
|
'market/klines': 1,
|
|
'market/depth': 1,
|
|
'market/ticker': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'trade/leverage': 2,
|
|
'trade/forceOrders': 2,
|
|
'trade/allFillOrders': 2,
|
|
'trade/openOrders': 2,
|
|
'trade/orderDetail': 2,
|
|
'trade/orderHistory': 2,
|
|
'trade/marginType': 2,
|
|
'user/commissionRate': 2,
|
|
'user/positions': 2,
|
|
'user/balance': 2,
|
|
},
|
|
'post': {
|
|
'trade/order': 2,
|
|
'trade/leverage': 2,
|
|
'trade/allOpenOrders': 2,
|
|
'trade/closeAllPositions': 2,
|
|
'trade/marginType': 2,
|
|
'trade/positionMargin': 2,
|
|
},
|
|
'delete': {
|
|
'trade/allOpenOrders': 2, # post method in doc
|
|
'trade/cancelOrder': 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'contract': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'allPosition': 2,
|
|
'allOrders': 2,
|
|
'balance': 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'wallets': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'capital/config/getall': 5,
|
|
'capital/deposit/address': 5,
|
|
'capital/innerTransfer/records': 1,
|
|
'capital/subAccount/deposit/address': 5,
|
|
'capital/deposit/subHisrec': 2,
|
|
'capital/subAccount/innerTransfer/records': 1,
|
|
'capital/deposit/riskRecords': 5,
|
|
},
|
|
'post': {
|
|
'capital/withdraw/apply': 5,
|
|
'capital/innerTransfer/apply': 5,
|
|
'capital/subAccountInnerTransfer/apply': 2,
|
|
'capital/deposit/createSubAddress': 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'subAccount': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'list': 10,
|
|
'assets': 2,
|
|
'allAccountBalance': 2,
|
|
},
|
|
'post': {
|
|
'create': 10,
|
|
'apiKey/create': 2,
|
|
'apiKey/edit': 2,
|
|
'apiKey/del': 2,
|
|
'updateStatus': 10,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'account': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'uid': 1,
|
|
'apiKey/query': 2,
|
|
'account/apiPermissions': 5,
|
|
'allAccountBalance': 2,
|
|
},
|
|
'post': {
|
|
'innerTransfer/authorizeSubAccount': 1,
|
|
},
|
|
},
|
|
},
|
|
'transfer': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'subAccount/asset/transferHistory': 1,
|
|
},
|
|
'post': {
|
|
'subAccount/transferAsset/supportCoins': 1,
|
|
'subAccount/transferAsset': 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'user': {
|
|
'auth': {
|
|
'private': {
|
|
'post': {
|
|
'userDataStream': 2,
|
|
},
|
|
'put': {
|
|
'userDataStream': 2,
|
|
},
|
|
'delete': {
|
|
'userDataStream': 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'copyTrading': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'swap/trace/currentTrack': 2,
|
|
},
|
|
'post': {
|
|
'swap/trace/closeTrackOrder': 2,
|
|
'swap/trace/setTPSL': 2,
|
|
'spot/trader/sellOrder': 10,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'api': {
|
|
'v3': {
|
|
'private': {
|
|
'get': {
|
|
'asset/transfer': 1,
|
|
'asset/transferRecord': 5,
|
|
'capital/deposit/hisrec': 1,
|
|
'capital/withdraw/history': 1,
|
|
},
|
|
'post': {
|
|
'post/asset/transfer': 1,
|
|
},
|
|
},
|
|
},
|
|
'asset': {
|
|
'v1': {
|
|
'private': {
|
|
'post': {
|
|
'transfer': 5,
|
|
},
|
|
},
|
|
'public': {
|
|
'get': {
|
|
'transfer/supportCoins': 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'agent': {
|
|
'v1': {
|
|
'private': {
|
|
'get': {
|
|
'account/inviteAccountList': 5,
|
|
'reward/commissionDataList': 5,
|
|
'account/inviteRelationCheck': 5,
|
|
'asset/depositDetailList': 5,
|
|
'reward/third/commissionDataList': 5,
|
|
'asset/partnerData': 5,
|
|
'commissionDataList/referralCode': 5,
|
|
'account/superiorCheck': 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'timeframes': {
|
|
'1m': '1m',
|
|
'3m': '3m',
|
|
'5m': '5m',
|
|
'15m': '15m',
|
|
'30m': '30m',
|
|
'1h': '1h',
|
|
'2h': '2h',
|
|
'4h': '4h',
|
|
'6h': '6h',
|
|
'12h': '12h',
|
|
'1d': '1d',
|
|
'3d': '3d',
|
|
'1w': '1w',
|
|
'1M': '1M',
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'exceptions': {
|
|
'exact': {
|
|
'400': BadRequest,
|
|
'401': AuthenticationError,
|
|
'403': PermissionDenied,
|
|
'404': BadRequest,
|
|
'429': DDoSProtection,
|
|
'418': PermissionDenied,
|
|
'500': ExchangeError,
|
|
'504': ExchangeError,
|
|
'100001': AuthenticationError,
|
|
'100412': AuthenticationError,
|
|
'100202': InsufficientFunds,
|
|
'100204': BadRequest,
|
|
'100400': BadRequest,
|
|
'100410': OperationFailed, # {"code":100410,"msg":"The current system is busy, please try again later"}
|
|
'100421': BadSymbol, # {"code":100421,"msg":"This pair is currently restricted from API trading","debugMsg":""}
|
|
'100440': ExchangeError,
|
|
'100500': OperationFailed, # {"code":100500,"msg":"The current system is busy, please try again later","debugMsg":""}
|
|
'100503': ExchangeError,
|
|
'80001': BadRequest,
|
|
'80012': InsufficientFunds, # {"code":80012,"msg":"{\"Code\":101253,\"Msg\":\"margin is not enough\"}}
|
|
'80014': BadRequest,
|
|
'80016': OrderNotFound,
|
|
'80017': OrderNotFound,
|
|
'100414': AccountSuspended, # {"code":100414,"msg":"Code: 100414, Msg: risk control check fail,code(1)","debugMsg":""}
|
|
'100419': PermissionDenied, # {"code":100419,"msg":"IP does not match IP whitelist","success":false,"timestamp":1705274099347}
|
|
'100437': BadRequest, # {"code":100437,"msg":"The withdrawal amount is lower than the minimum limit, please re-enter.","timestamp":1689258588845}
|
|
'101204': InsufficientFunds, # {"code":101204,"msg":"","data":{}}
|
|
'110425': InvalidOrder, # {"code":110425,"msg":"Please ensure that the minimum nominal value of the order placed must be greater than 2u","data":{}}
|
|
'Insufficient assets': InsufficientFunds, # {"transferErrorMsg":"Insufficient assets"}
|
|
'illegal transferType': BadRequest, # {"transferErrorMsg":"illegal transferType"}
|
|
},
|
|
'broad': {},
|
|
},
|
|
'commonCurrencies': {
|
|
'SNOW': 'Snowman', # Snowman vs SnowSwap conflict
|
|
'OMNI': 'OmniCat',
|
|
'NAP': '$NAP', # NAP on SOL = SNAP
|
|
'TRUMP': 'TRUMPMAGA',
|
|
'TRUMPSOL': 'TRUMP',
|
|
},
|
|
'options': {
|
|
'defaultType': 'spot',
|
|
'accountsByType': {
|
|
'funding': 'fund',
|
|
'spot': 'spot',
|
|
'future': 'stdFutures',
|
|
'swap': 'USDTMPerp',
|
|
'linear': 'USDTMPerp',
|
|
'inverse': 'coinMPerp',
|
|
},
|
|
'accountsById': {
|
|
'fund': 'funding',
|
|
'spot': 'spot',
|
|
'stdFutures': 'future',
|
|
'USDTMPerp': 'linear',
|
|
'coinMPerp': 'inverse',
|
|
},
|
|
'recvWindow': 5 * 1000, # 5 sec
|
|
'broker': 'CCXT',
|
|
'defaultNetworks': {
|
|
'ETH': 'ETH',
|
|
'USDT': 'ERC20',
|
|
'USDC': 'ERC20',
|
|
'BTC': 'BTC',
|
|
'LTC': 'LTC',
|
|
},
|
|
'networks': {
|
|
'ARBITRUM': 'ARB',
|
|
'MATIC': 'POLYGON',
|
|
'ZKSYNC': 'ZKSYNCERA',
|
|
'AVAXC': 'AVAX-C',
|
|
'HBAR': 'HEDERA',
|
|
},
|
|
},
|
|
'features': {
|
|
'defaultForLinear': {
|
|
'sandbox': True,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': {
|
|
'last': True,
|
|
'mark': True,
|
|
'index': True,
|
|
},
|
|
'triggerDirection': False,
|
|
'stopLossPrice': True,
|
|
'takeProfitPrice': True,
|
|
'attachedStopLossTakeProfit': {
|
|
'triggerPriceType': {
|
|
'last': True,
|
|
'mark': True,
|
|
'index': True,
|
|
},
|
|
'price': True,
|
|
},
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': True,
|
|
'trailing': True,
|
|
'leverage': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'marketBuyByCost': True,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': {
|
|
'max': 5,
|
|
},
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 512, # 512 days for 'allFillOrders', 1000 days for 'fillOrders'
|
|
'daysBack': 30, # 30 for 'allFillOrders', 7 for 'fillHistory'
|
|
'untilDays': 30, # 30 for 'allFillOrders', 7 for 'fillHistory'
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 20000, # since epoch
|
|
'untilDays': 7,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchClosedOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': None,
|
|
'daysBackCanceled': None,
|
|
'untilDays': 7,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOHLCV': {
|
|
'limit': 1440,
|
|
},
|
|
},
|
|
'defaultForInverse': {
|
|
'extends': 'defaultForLinear',
|
|
'fetchMyTrades': {
|
|
'limit': 1000,
|
|
'daysBack': None,
|
|
'untilDays': None,
|
|
},
|
|
'fetchOrders': None,
|
|
},
|
|
#
|
|
'spot': {
|
|
'extends': 'defaultForLinear',
|
|
'fetchCurrencies': {
|
|
'private': True,
|
|
},
|
|
'createOrder': {
|
|
'triggerPriceType': None,
|
|
'attachedStopLossTakeProfit': None,
|
|
'trailing': False,
|
|
},
|
|
'fetchMyTrades': {
|
|
'limit': 1000,
|
|
'daysBack': 1,
|
|
'untilDays': 1,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': {
|
|
'limit': 100,
|
|
'untilDays': None,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': {
|
|
'extends': 'defaultForLinear',
|
|
},
|
|
'inverse': {
|
|
'extends': 'defaultForInverse',
|
|
},
|
|
},
|
|
'defaultForFuture': {
|
|
'extends': 'defaultForLinear',
|
|
'fetchOrders': None,
|
|
},
|
|
'future': {
|
|
'linear': {
|
|
'extends': 'defaultForFuture',
|
|
},
|
|
'inverse': {
|
|
'extends': 'defaultForFuture',
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
def fetch_time(self, params={}) -> Int:
|
|
"""
|
|
fetches the current integer timestamp in milliseconds from the bingx server
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/base-info.html#Get%20Server%20Time
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int: the current integer timestamp in milliseconds from the bingx server
|
|
"""
|
|
response = self.swapV2PublicGetServerTime(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "serverTime": 1675319535362
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data')
|
|
return self.safe_integer(data, 'serverTime')
|
|
|
|
def fetch_currencies(self, params={}) -> Currencies:
|
|
"""
|
|
fetches all available currencies on an exchange
|
|
|
|
https://bingx-api.github.io/docs/#/common/account-api.html#All%20Coins
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an associative dictionary of currencies
|
|
"""
|
|
if not self.check_required_credentials(False):
|
|
return {}
|
|
isSandbox = self.safe_bool(self.options, 'sandboxMode', False)
|
|
if isSandbox:
|
|
return {}
|
|
response = self.walletsV1PrivateGetCapitalConfigGetall(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "timestamp": 1702623271476,
|
|
# "data": [
|
|
# {
|
|
# "coin": "BTC",
|
|
# "name": "BTC",
|
|
# "networkList": [
|
|
# {
|
|
# "name": "BTC",
|
|
# "network": "BTC",
|
|
# "isDefault": True,
|
|
# "minConfirm": 2,
|
|
# "withdrawEnable": True,
|
|
# "depositEnable": True,
|
|
# "withdrawFee": "0.0006",
|
|
# "withdrawMax": "1.17522",
|
|
# "withdrawMin": "0.0005",
|
|
# "depositMin": "0.0002"
|
|
# },
|
|
# {
|
|
# "name": "BTC",
|
|
# "network": "BEP20",
|
|
# "isDefault": False,
|
|
# "minConfirm": 15,
|
|
# "withdrawEnable": True,
|
|
# "depositEnable": True,
|
|
# "withdrawFee": "0.0000066",
|
|
# "withdrawMax": "1.17522",
|
|
# "withdrawMin": "0.0000066",
|
|
# "depositMin": "0.0002"
|
|
# }
|
|
# ]
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
result: dict = {}
|
|
for i in range(0, len(data)):
|
|
entry = data[i]
|
|
currencyId = self.safe_string(entry, 'coin')
|
|
code = self.safe_currency_code(currencyId)
|
|
name = self.safe_string(entry, 'name')
|
|
networkList = self.safe_list(entry, 'networkList')
|
|
networks: dict = {}
|
|
for j in range(0, len(networkList)):
|
|
rawNetwork = networkList[j]
|
|
network = self.safe_string(rawNetwork, 'network')
|
|
networkCode = self.network_id_to_code(network)
|
|
limits: dict = {
|
|
'withdraw': {
|
|
'min': self.safe_number(rawNetwork, 'withdrawMin'),
|
|
'max': self.safe_number(rawNetwork, 'withdrawMax'),
|
|
},
|
|
'deposit': {
|
|
'min': self.safe_number(rawNetwork, 'depositMin'),
|
|
'max': None,
|
|
},
|
|
}
|
|
precision = self.parse_number(self.parse_precision(self.safe_string(rawNetwork, 'withdrawPrecision')))
|
|
networks[networkCode] = {
|
|
'info': rawNetwork,
|
|
'id': network,
|
|
'network': networkCode,
|
|
'fee': self.safe_number(rawNetwork, 'withdrawFee'),
|
|
'active': None,
|
|
'deposit': self.safe_bool(rawNetwork, 'depositEnable'),
|
|
'withdraw': self.safe_bool(rawNetwork, 'withdrawEnable'),
|
|
'precision': precision,
|
|
'limits': limits,
|
|
}
|
|
if not (code in result): # the exchange could return the same currency with different networks
|
|
result[code] = {
|
|
'info': entry,
|
|
'code': code,
|
|
'id': currencyId,
|
|
'precision': None,
|
|
'name': name,
|
|
'active': None,
|
|
'deposit': None,
|
|
'withdraw': None,
|
|
'networks': networks,
|
|
'fee': None,
|
|
'limits': None,
|
|
'type': 'crypto', # only cryptos now
|
|
}
|
|
else:
|
|
existing = result[code]
|
|
existingNetworks = self.safe_dict(existing, 'networks', {})
|
|
newNetworkCodes = list(networks.keys())
|
|
for j in range(0, len(newNetworkCodes)):
|
|
newNetworkCode = newNetworkCodes[j]
|
|
if not (newNetworkCode in existingNetworks):
|
|
existingNetworks[newNetworkCode] = networks[newNetworkCode]
|
|
result[code]['networks'] = existingNetworks
|
|
codes = list(result.keys())
|
|
for i in range(0, len(codes)):
|
|
code = codes[i]
|
|
currency = result[code]
|
|
result[code] = self.safe_currency_structure(currency)
|
|
return result
|
|
|
|
def fetch_spot_markets(self, params) -> List[Market]:
|
|
response = self.spotV1PublicGetCommonSymbols(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "symbols": [
|
|
# {
|
|
# "symbol": "GEAR-USDT",
|
|
# "minQty": 735, # deprecated
|
|
# "maxQty": 2941177, # deprecated.
|
|
# "minNotional": 5,
|
|
# "maxNotional": 20000,
|
|
# "status": 1,
|
|
# "tickSize": 0.000001,
|
|
# "stepSize": 1,
|
|
# "apiStateSell": True,
|
|
# "apiStateBuy": True,
|
|
# "timeOnline": 0,
|
|
# "offTime": 0,
|
|
# "maintainTime": 0
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data')
|
|
markets = self.safe_list(data, 'symbols', [])
|
|
return self.parse_markets(markets)
|
|
|
|
def fetch_swap_markets(self, params):
|
|
response = self.swapV2PublicGetQuoteContracts(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": [
|
|
# {
|
|
# "contractId": "100",
|
|
# "symbol": "BTC-USDT",
|
|
# "size": "0.0001",
|
|
# "quantityPrecision": "4",
|
|
# "pricePrecision": "1",
|
|
# "feeRate": "0.0005",
|
|
# "makerFeeRate": "0.0002",
|
|
# "takerFeeRate": "0.0005",
|
|
# "tradeMinLimit": "0",
|
|
# "tradeMinQuantity": "0.0001",
|
|
# "tradeMinUSDT": "2",
|
|
# "maxLongLeverage": "125",
|
|
# "maxShortLeverage": "125",
|
|
# "currency": "USDT",
|
|
# "asset": "BTC",
|
|
# "status": "1",
|
|
# "apiStateOpen": "true",
|
|
# "apiStateClose": "true",
|
|
# "ensureTrigger": True,
|
|
# "triggerFeeRate": "0.00020000"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
markets = self.safe_list(response, 'data', [])
|
|
return self.parse_markets(markets)
|
|
|
|
def fetch_inverse_swap_markets(self, params):
|
|
response = self.cswapV1PublicGetMarketContracts(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720074487610,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BNB-USD",
|
|
# "pricePrecision": 2,
|
|
# "minTickSize": "10",
|
|
# "minTradeValue": "10",
|
|
# "minQty": "1.00000000",
|
|
# "status": 1,
|
|
# "timeOnline": 1713175200000
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
markets = self.safe_list(response, 'data', [])
|
|
return self.parse_markets(markets)
|
|
|
|
def parse_market(self, market: dict) -> Market:
|
|
id = self.safe_string(market, 'symbol')
|
|
symbolParts = id.split('-')
|
|
baseId = symbolParts[0]
|
|
quoteId = symbolParts[1]
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
currency = self.safe_string(market, 'currency')
|
|
checkIsInverse = False
|
|
checkIsLinear = True
|
|
minTickSize = self.safe_number(market, 'minTickSize')
|
|
if minTickSize is not None:
|
|
# inverse swap market
|
|
currency = baseId
|
|
checkIsInverse = True
|
|
checkIsLinear = False
|
|
settle = self.safe_currency_code(currency)
|
|
pricePrecision = self.safe_number(market, 'tickSize')
|
|
if pricePrecision is None:
|
|
pricePrecision = self.parse_number(self.parse_precision(self.safe_string(market, 'pricePrecision')))
|
|
quantityPrecision = self.safe_number(market, 'stepSize')
|
|
if quantityPrecision is None:
|
|
quantityPrecision = self.parse_number(self.parse_precision(self.safe_string(market, 'quantityPrecision')))
|
|
type = 'swap' if (settle is not None) else 'spot'
|
|
spot = type == 'spot'
|
|
swap = type == 'swap'
|
|
symbol = base + '/' + quote
|
|
if settle is not None:
|
|
symbol += ':' + settle
|
|
fees = self.safe_dict(self.fees, type, {})
|
|
contractSize = self.parse_number('1') if (swap) else None
|
|
isActive = False
|
|
if (self.safe_string(market, 'apiStateOpen') == 'true') and (self.safe_string(market, 'apiStateClose') == 'true'):
|
|
isActive = True # swap active
|
|
elif self.safe_bool(market, 'apiStateSell') and self.safe_bool(market, 'apiStateBuy') and (self.safe_string(market, 'status') == '1'):
|
|
isActive = True # spot active
|
|
isInverse = None if (spot) else checkIsInverse
|
|
isLinear = None if (spot) else checkIsLinear
|
|
minAmount = None
|
|
if not spot:
|
|
minAmount = self.safe_number_2(market, 'minQty', 'tradeMinQuantity')
|
|
timeOnline = self.safe_integer(market, 'timeOnline')
|
|
if timeOnline == 0:
|
|
timeOnline = None
|
|
return self.safe_market_structure({
|
|
'id': id,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': currency,
|
|
'type': type,
|
|
'spot': spot,
|
|
'margin': False,
|
|
'swap': swap,
|
|
'future': False,
|
|
'option': False,
|
|
'active': isActive,
|
|
'contract': swap,
|
|
'linear': isLinear,
|
|
'inverse': isInverse,
|
|
'taker': self.safe_number(fees, 'taker'),
|
|
'maker': self.safe_number(fees, 'maker'),
|
|
'feeSide': self.safe_string(fees, 'feeSide'),
|
|
'contractSize': contractSize,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': quantityPrecision,
|
|
'price': pricePrecision,
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': minAmount,
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': minTickSize,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': self.safe_number_n(market, ['minNotional', 'tradeMinUSDT', 'minTradeValue']),
|
|
'max': self.safe_number(market, 'maxNotional'),
|
|
},
|
|
},
|
|
'created': timeOnline,
|
|
'info': market,
|
|
})
|
|
|
|
def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for bingx
|
|
|
|
https://bingx-api.github.io/docs/#/spot/market-api.html#Query%20Symbols
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#Contract%20Information
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/market-api.html#Contract%20Information
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
requests = [self.fetch_swap_markets(params)]
|
|
isSandbox = self.safe_bool(self.options, 'sandboxMode', False)
|
|
if not isSandbox:
|
|
requests.append(self.fetch_inverse_swap_markets(params))
|
|
requests.append(self.fetch_spot_markets(params)) # sandbox is swap only
|
|
promises = requests
|
|
linearSwapMarkets = self.safe_list(promises, 0, [])
|
|
inverseSwapMarkets = self.safe_list(promises, 1, [])
|
|
spotMarkets = self.safe_list(promises, 2, [])
|
|
swapMarkets = self.array_concat(linearSwapMarkets, inverseSwapMarkets)
|
|
return self.array_concat(spotMarkets, swapMarkets)
|
|
|
|
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://bingx-api.github.io/docs/#/swapV2/market-api.html#K-Line%20Data
|
|
https://bingx-api.github.io/docs/#/spot/market-api.html#Candlestick%20chart%20data
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#%20K-Line%20Data
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/market-api.html#Mark%20Price%20Kline/Candlestick%20Data
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/market-api.html#Get%20K-line%20Data
|
|
|
|
: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 in ms of the latest candle to fetch
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available 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, 1440)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
request['interval'] = self.safe_string(self.timeframes, timeframe, timeframe)
|
|
if since is not None:
|
|
request['startTime'] = max(since - 1, 0)
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer_2(params, 'until', 'endTime')
|
|
if until is not None:
|
|
params = self.omit(params, ['until'])
|
|
request['endTime'] = until
|
|
response = None
|
|
if market['spot']:
|
|
response = self.spotV1PublicGetMarketKline(self.extend(request, params))
|
|
else:
|
|
if market['inverse']:
|
|
response = self.cswapV1PublicGetMarketKlines(self.extend(request, params))
|
|
else:
|
|
price = self.safe_string(params, 'price')
|
|
params = self.omit(params, 'price')
|
|
if price == 'mark':
|
|
response = self.swapV1PublicGetMarketMarkPriceKlines(self.extend(request, params))
|
|
else:
|
|
response = self.swapV3PublicGetQuoteKlines(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": [
|
|
# {
|
|
# "open": "19396.8",
|
|
# "close": "19394.4",
|
|
# "high": "19397.5",
|
|
# "low": "19385.7",
|
|
# "volume": "110.05",
|
|
# "time": 1666583700000
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
# fetchMarkOHLCV
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": [
|
|
# {
|
|
# "open": "42191.7",
|
|
# "close": "42189.5",
|
|
# "high": "42196.5",
|
|
# "low": "42189.5",
|
|
# "volume": "0.00",
|
|
# "openTime": 1706508840000,
|
|
# "closeTime": 1706508840000
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
ohlcvs = self.safe_value(response, 'data', [])
|
|
if not isinstance(ohlcvs, list):
|
|
ohlcvs = [ohlcvs]
|
|
return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# {
|
|
# "open": "19394.4",
|
|
# "close": "19379.0",
|
|
# "high": "19394.4",
|
|
# "low": "19368.3",
|
|
# "volume": "167.44",
|
|
# "time": 1666584000000
|
|
# }
|
|
#
|
|
# fetchMarkOHLCV
|
|
#
|
|
# {
|
|
# "open": "42191.7",
|
|
# "close": "42189.5",
|
|
# "high": "42196.5",
|
|
# "low": "42189.5",
|
|
# "volume": "0.00",
|
|
# "openTime": 1706508840000,
|
|
# "closeTime": 1706508840000
|
|
# }
|
|
# spot
|
|
# [
|
|
# 1691402580000,
|
|
# 29093.61,
|
|
# 29093.93,
|
|
# 29087.73,
|
|
# 29093.24,
|
|
# 0.59,
|
|
# 1691402639999,
|
|
# 17221.07
|
|
# ]
|
|
#
|
|
if isinstance(ohlcv, list):
|
|
return [
|
|
self.safe_integer(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),
|
|
]
|
|
return [
|
|
self.safe_integer_2(ohlcv, 'time', 'closeTime'),
|
|
self.safe_number(ohlcv, 'open'),
|
|
self.safe_number(ohlcv, 'high'),
|
|
self.safe_number(ohlcv, 'low'),
|
|
self.safe_number(ohlcv, 'close'),
|
|
self.safe_number(ohlcv, 'volume'),
|
|
]
|
|
|
|
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a particular symbol
|
|
|
|
https://bingx-api.github.io/docs/#/spot/market-api.html#Query%20transaction%20records
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#The%20latest%20Trade%20of%20a%20Trading%20Pair
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = min(limit, 100) # avoid API exception "limit should less than 100"
|
|
response = None
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('fetchTrades', market, params)
|
|
if marketType == 'spot':
|
|
response = self.spotV1PublicGetMarketTrades(self.extend(request, params))
|
|
else:
|
|
response = self.swapV2PublicGetQuoteTrades(self.extend(request, params))
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "data": [
|
|
# {
|
|
# "id": 43148253,
|
|
# "price": 25714.71,
|
|
# "qty": 1.674571,
|
|
# "time": 1655085975589,
|
|
# "buyerMaker": False
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# swap
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"",
|
|
# "data":[
|
|
# {
|
|
# "time": 1672025549368,
|
|
# "isBuyerMaker": True,
|
|
# "price": "16885.0",
|
|
# "qty": "3.3002",
|
|
# "quoteQty": "55723.87"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
trades = self.safe_list(response, 'data', [])
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# spot fetchTrades
|
|
#
|
|
# {
|
|
# "id": 43148253,
|
|
# "price": 25714.71,
|
|
# "qty": 1.674571,
|
|
# "time": 1655085975589,
|
|
# "buyerMaker": False
|
|
# }
|
|
#
|
|
# spot fetchMyTrades
|
|
#
|
|
# {
|
|
# "symbol": "LTC-USDT",
|
|
# "id": 36237072,
|
|
# "orderId": 1674069326895775744,
|
|
# "price": "85.891",
|
|
# "qty": "0.0582",
|
|
# "quoteQty": "4.9988562000000005",
|
|
# "commission": -0.00005820000000000001,
|
|
# "commissionAsset": "LTC",
|
|
# "time": 1687964205000,
|
|
# "isBuyer": True,
|
|
# "isMaker": False
|
|
# }
|
|
#
|
|
# swap fetchTrades
|
|
#
|
|
# {
|
|
# "time": 1672025549368,
|
|
# "isBuyerMaker": True,
|
|
# "price": "16885.0",
|
|
# "qty": "3.3002",
|
|
# "quoteQty": "55723.87"
|
|
# }
|
|
#
|
|
# swap fetchMyTrades
|
|
#
|
|
# {
|
|
# "volume": "0.1",
|
|
# "price": "106.75",
|
|
# "amount": "10.6750",
|
|
# "commission": "-0.0053",
|
|
# "currency": "USDT",
|
|
# "orderId": "1676213270274379776",
|
|
# "liquidatedPrice": "0.00",
|
|
# "liquidatedMarginRatio": "0.00",
|
|
# "filledTime": "2023-07-04T20:56:01.000+0800"
|
|
# }
|
|
#
|
|
# ws spot
|
|
#
|
|
# {
|
|
# "E": 1690214529432,
|
|
# "T": 1690214529386,
|
|
# "e": "trade",
|
|
# "m": True,
|
|
# "p": "29110.19",
|
|
# "q": "0.1868",
|
|
# "s": "BTC-USDT",
|
|
# "t": "57903921"
|
|
# }
|
|
#
|
|
# ws linear swap
|
|
#
|
|
# {
|
|
# "q": "0.0421",
|
|
# "p": "29023.5",
|
|
# "T": 1690221401344,
|
|
# "m": False,
|
|
# "s": "BTC-USDT"
|
|
# }
|
|
#
|
|
# ws inverse swap
|
|
#
|
|
# {
|
|
# "e": "trade",
|
|
# "E": 1722920589665,
|
|
# "s": "BTC-USD",
|
|
# "t": "39125001",
|
|
# "p": "55360.0",
|
|
# "q": "1",
|
|
# "T": 1722920589582,
|
|
# "m": False
|
|
# }
|
|
#
|
|
# inverse swap fetchMyTrades
|
|
#
|
|
# {
|
|
# "orderId": "1817441228670648320",
|
|
# "symbol": "SOL-USD",
|
|
# "type": "MARKET",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "tradeId": "97244554",
|
|
# "volume": "2",
|
|
# "tradePrice": "182.652",
|
|
# "amount": "20.00000000",
|
|
# "realizedPnl": "0.00000000",
|
|
# "commission": "-0.00005475",
|
|
# "currency": "SOL",
|
|
# "buyer": True,
|
|
# "maker": False,
|
|
# "tradeTime": 1722146730000
|
|
# }
|
|
#
|
|
time = self.safe_integer_n(trade, ['time', 'filledTm', 'T', 'tradeTime'])
|
|
datetimeId = self.safe_string(trade, 'filledTm')
|
|
if datetimeId is not None:
|
|
time = self.parse8601(datetimeId)
|
|
if time == 0:
|
|
time = None
|
|
cost = self.safe_string(trade, 'quoteQty')
|
|
# type = 'spot' if (cost is None) else 'swap'; self is not reliable
|
|
currencyId = self.safe_string_n(trade, ['currency', 'N', 'commissionAsset'])
|
|
currencyCode = self.safe_currency_code(currencyId)
|
|
m = self.safe_bool(trade, 'm')
|
|
marketId = self.safe_string_2(trade, 's', 'symbol')
|
|
isBuyerMaker = self.safe_bool_n(trade, ['buyerMaker', 'isBuyerMaker', 'maker'])
|
|
takeOrMaker = None
|
|
if (isBuyerMaker is not None) or (m is not None):
|
|
takeOrMaker = 'maker' if (isBuyerMaker or m) else 'taker'
|
|
side = self.safe_string_lower_2(trade, 'side', 'S')
|
|
if side is None:
|
|
if (isBuyerMaker is not None) or (m is not None):
|
|
side = 'sell' if (isBuyerMaker or m) else 'buy'
|
|
takeOrMaker = 'taker'
|
|
isBuyer = self.safe_bool(trade, 'isBuyer')
|
|
if isBuyer is not None:
|
|
side = 'buy' if isBuyer else 'sell'
|
|
isMaker = self.safe_bool(trade, 'isMaker')
|
|
if isMaker is not None:
|
|
takeOrMaker = 'maker' if isMaker else 'taker'
|
|
amount = self.safe_string_n(trade, ['qty', 'amount', 'q'])
|
|
if (market is not None) and market['swap'] and ('volume' in trade):
|
|
# private trade returns num of contracts instead of base currency(as the order-related methods do)
|
|
contractSize = self.safe_string(market['info'], 'tradeMinQuantity')
|
|
volume = self.safe_string(trade, 'volume')
|
|
amount = Precise.string_mul(volume, contractSize)
|
|
return self.safe_trade({
|
|
'id': self.safe_string_n(trade, ['id', 't']),
|
|
'info': trade,
|
|
'timestamp': time,
|
|
'datetime': self.iso8601(time),
|
|
'symbol': self.safe_symbol(marketId, market, '-'),
|
|
'order': self.safe_string_2(trade, 'orderId', 'i'),
|
|
'type': self.safe_string_lower(trade, 'o'),
|
|
'side': self.parse_order_side(side),
|
|
'takerOrMaker': takeOrMaker,
|
|
'price': self.safe_string_n(trade, ['price', 'p', 'tradePrice']),
|
|
'amount': amount,
|
|
'cost': cost,
|
|
'fee': {
|
|
'cost': self.parse_number(Precise.string_abs(self.safe_string_2(trade, 'commission', 'n'))),
|
|
'currency': currencyCode,
|
|
},
|
|
}, market)
|
|
|
|
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://bingx-api.github.io/docs/#/spot/market-api.html#Query%20depth%20information
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#Get%20Market%20Depth
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/market-api.html#Query%20Depth%20Data
|
|
|
|
: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'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = None
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('fetchOrderBook', market, params)
|
|
if marketType == 'spot':
|
|
response = self.spotV1PublicGetMarketDepth(self.extend(request, params))
|
|
else:
|
|
if market['inverse']:
|
|
response = self.cswapV1PublicGetMarketDepth(self.extend(request, params))
|
|
else:
|
|
response = self.swapV2PublicGetQuoteDepth(self.extend(request, params))
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "timestamp":1743240504535,
|
|
# "data":{
|
|
# "bids":[
|
|
# ["83775.39","1.981875"],
|
|
# ["83775.38","0.001076"],
|
|
# ["83775.34","0.254716"],
|
|
# ],
|
|
# "asks":[
|
|
# ["83985.40","0.000013"],
|
|
# ["83980.00","0.000011"],
|
|
# ["83975.70","0.000061000000000000005"],
|
|
# ],
|
|
# "ts":1743240504535,
|
|
# "lastUpdateId":13565639906
|
|
# }
|
|
# }
|
|
#
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"",
|
|
# "data":{
|
|
# "T":1743240836255,
|
|
# "bids":[
|
|
# ["83760.7","7.0861"],
|
|
# ["83760.6","0.0044"],
|
|
# ["83757.7","1.9526"],
|
|
# ],
|
|
# "asks":[
|
|
# ["83784.3","8.3531"],
|
|
# ["83782.8","23.7289"],
|
|
# ["83780.1","18.0617"],
|
|
# ],
|
|
# "bidsCoin":[
|
|
# ["83760.7","0.0007"],
|
|
# ["83760.6","0.0000"],
|
|
# ["83757.7","0.0002"],
|
|
# ],
|
|
# "asksCoin":[
|
|
# ["83784.3","0.0008"],
|
|
# ["83782.8","0.0024"],
|
|
# ["83780.1","0.0018"],
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"",
|
|
# "timestamp":1743240979146,
|
|
# "data":{
|
|
# "T":1743240978691,
|
|
# "bids":[
|
|
# ["83611.4","241.0"],
|
|
# ["83611.3","1.0"],
|
|
# ["83602.9","666.0"],
|
|
# ],
|
|
# "asks":[
|
|
# ["83645.0","4253.0"],
|
|
# ["83640.5","3188.0"],
|
|
# ["83636.0","5540.0"],
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
orderbook = self.safe_dict(response, 'data', {})
|
|
nonce = self.safe_integer(orderbook, 'lastUpdateId')
|
|
timestamp = self.safe_integer_2(orderbook, 'T', 'ts')
|
|
result = self.parse_order_book(orderbook, market['symbol'], timestamp, 'bids', 'asks', 0, 1)
|
|
result['nonce'] = nonce
|
|
return result
|
|
|
|
def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
|
|
"""
|
|
fetch the current funding rate
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#Current%20Funding%20Rate
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/market-api.html#Price%20&%20Current%20Funding%20Rate
|
|
|
|
: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)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
if market['inverse']:
|
|
response = self.cswapV1PublicGetMarketPremiumIndex(self.extend(request, params))
|
|
else:
|
|
response = self.swapV2PublicGetQuotePremiumIndex(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"",
|
|
# "data":[
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "markPrice": "16884.5",
|
|
# "indexPrice": "16886.9",
|
|
# "lastFundingRate": "0.0001",
|
|
# "nextFundingTime": 1672041600000
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data')
|
|
return self.parse_funding_rate(data, market)
|
|
|
|
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
|
|
"""
|
|
fetch the current funding rate for multiple symbols
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#Current%20Funding%20Rate
|
|
|
|
:param str[] [symbols]: list of unified market symbols
|
|
: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-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols, 'swap', True)
|
|
response = self.swapV2PublicGetQuotePremiumIndex(self.extend(params))
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_funding_rates(data, symbols)
|
|
|
|
def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "markPrice": "16884.5",
|
|
# "indexPrice": "16886.9",
|
|
# "lastFundingRate": "0.0001",
|
|
# "nextFundingTime": 1672041600000
|
|
# }
|
|
#
|
|
marketId = self.safe_string(contract, 'symbol')
|
|
nextFundingTimestamp = self.safe_integer(contract, 'nextFundingTime')
|
|
return {
|
|
'info': contract,
|
|
'symbol': self.safe_symbol(marketId, market, '-', 'swap'),
|
|
'markPrice': self.safe_number(contract, 'markPrice'),
|
|
'indexPrice': self.safe_number(contract, 'indexPrice'),
|
|
'interestRate': None,
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'fundingRate': self.safe_number(contract, 'lastFundingRate'),
|
|
'fundingTimestamp': None,
|
|
'fundingDatetime': None,
|
|
'nextFundingRate': None,
|
|
'nextFundingTimestamp': nextFundingTimestamp,
|
|
'nextFundingDatetime': self.iso8601(nextFundingTimestamp),
|
|
'previousFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
'interval': None,
|
|
}
|
|
|
|
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical funding rate prices
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#Funding%20Rate%20History
|
|
|
|
:param str symbol: unified symbol of the market to fetch the funding rate history for
|
|
:param int [since]: timestamp in ms of the earliest funding rate to fetch
|
|
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest funding rate to fetch
|
|
:param boolean [params.paginate]: 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 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()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
until = self.safe_integer_2(params, 'until', 'startTime')
|
|
if until is not None:
|
|
params = self.omit(params, ['until'])
|
|
request['startTime'] = until
|
|
response = self.swapV2PublicGetQuoteFundingRate(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "msg":"",
|
|
# "data":[
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "fundingRate": "0.0001",
|
|
# "fundingTime": 1585684800000
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_funding_rate_histories(data, market, since, limit)
|
|
|
|
def parse_funding_rate_history(self, contract, market: Market = None):
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "fundingRate": "0.0001",
|
|
# "fundingTime": 1585684800000
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(contract, 'fundingTime')
|
|
return {
|
|
'info': contract,
|
|
'symbol': self.safe_symbol(self.safe_string(contract, 'symbol'), market, '-', 'swap'),
|
|
'fundingRate': self.safe_number(contract, 'fundingRate'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
|
|
def fetch_open_interest(self, symbol: str, params={}):
|
|
"""
|
|
retrieves the open interest of a trading pair
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/market-api.html#Get%20Swap%20Open%20Positions
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/market-api.html#Get%20Swap%20Open%20Positions
|
|
|
|
: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)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
if market['inverse']:
|
|
response = self.cswapV1PublicGetMarketOpenInterest(self.extend(request, params))
|
|
else:
|
|
response = self.swapV2PublicGetQuoteOpenInterest(self.extend(request, params))
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "openInterest": "3289641547.10",
|
|
# "symbol": "BTC-USDT",
|
|
# "time": 1672026617364
|
|
# }
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720328247986,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTC-USD",
|
|
# "openInterest": "749.1160",
|
|
# "timestamp": 1720310400000
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result: dict = {}
|
|
if market['inverse']:
|
|
data = self.safe_list(response, 'data', [])
|
|
result = self.safe_dict(data, 0, {})
|
|
else:
|
|
result = self.safe_dict(response, 'data', {})
|
|
return self.parse_open_interest(result, market)
|
|
|
|
def parse_open_interest(self, interest, market: Market = None):
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "openInterest": "3289641547.10",
|
|
# "symbol": "BTC-USDT",
|
|
# "time": 1672026617364
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USD",
|
|
# "openInterest": "749.1160",
|
|
# "timestamp": 1720310400000
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer_2(interest, 'time', 'timestamp')
|
|
id = self.safe_string(interest, 'symbol')
|
|
symbol = self.safe_symbol(id, market, '-', 'swap')
|
|
openInterest = self.safe_number(interest, 'openInterest')
|
|
return self.safe_open_interest({
|
|
'symbol': symbol,
|
|
'baseVolume': None,
|
|
'quoteVolume': None, # deprecated
|
|
'openInterestAmount': None,
|
|
'openInterestValue': openInterest,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'info': interest,
|
|
}, 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://bingx-api.github.io/docs/#/en-us/swapV2/market-api.html#Get%20Ticker
|
|
https://bingx-api.github.io/docs/#/en-us/spot/market-api.html#24-hour%20price%20changes
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/market-api.html#Query%2024-Hour%20Price%20Change
|
|
|
|
: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 = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
if market['spot']:
|
|
response = self.spotV1PublicGetTicker24hr(self.extend(request, params))
|
|
else:
|
|
if market['inverse']:
|
|
response = self.cswapV1PublicGetMarketTicker(self.extend(request, params))
|
|
else:
|
|
response = self.swapV2PublicGetQuoteTicker(self.extend(request, params))
|
|
#
|
|
# spot and swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720647285296,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "priceChange": "-2.418",
|
|
# "priceChangePercent": "-1.6900%",
|
|
# "lastPrice": "140.574",
|
|
# "lastQty": "1",
|
|
# "highPrice": "146.190",
|
|
# "lowPrice": "138.586",
|
|
# "volume": "1464648.00",
|
|
# "quoteVolume": "102928.12",
|
|
# "openPrice": "142.994",
|
|
# "closeTime": "1720647284976",
|
|
# "bidPrice": "140.573",
|
|
# "bidQty": "372",
|
|
# "askPrice": "140.577",
|
|
# "askQty": "58"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data')
|
|
if data is not None:
|
|
first = self.safe_dict(data, 0, {})
|
|
return self.parse_ticker(first, market)
|
|
dataDict = self.safe_dict(response, 'data', {})
|
|
return self.parse_ticker(dataDict, 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://bingx-api.github.io/docs/#/en-us/swapV2/market-api.html#Get%20Ticker
|
|
https://bingx-api.github.io/docs/#/en-us/spot/market-api.html#24-hour%20price%20changes
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/market-api.html#Query%2024-Hour%20Price%20Change
|
|
|
|
: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()
|
|
market = None
|
|
if symbols is not None:
|
|
symbols = self.market_symbols(symbols)
|
|
firstSymbol = self.safe_string(symbols, 0)
|
|
if firstSymbol is not None:
|
|
market = self.market(firstSymbol)
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('fetchTickers', market, params)
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('fetchTickers', market, params)
|
|
response = None
|
|
if type == 'spot':
|
|
response = self.spotV1PublicGetTicker24hr(params)
|
|
else:
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PublicGetMarketTicker(params)
|
|
else:
|
|
response = self.swapV2PublicGetQuoteTicker(params)
|
|
#
|
|
# spot and swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720647285296,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "priceChange": "-2.418",
|
|
# "priceChangePercent": "-1.6900%",
|
|
# "lastPrice": "140.574",
|
|
# "lastQty": "1",
|
|
# "highPrice": "146.190",
|
|
# "lowPrice": "138.586",
|
|
# "volume": "1464648.00",
|
|
# "quoteVolume": "102928.12",
|
|
# "openPrice": "142.994",
|
|
# "closeTime": "1720647284976",
|
|
# "bidPrice": "140.573",
|
|
# "bidQty": "372",
|
|
# "askPrice": "140.577",
|
|
# "askQty": "58"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
tickers = self.safe_list(response, 'data')
|
|
return self.parse_tickers(tickers, symbols)
|
|
|
|
def fetch_mark_price(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
fetches mark prices for the market
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/market-api.html#Mark%20Price%20and%20Funding%20Rate
|
|
|
|
: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 dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('fetchMarkPrice', market, params, 'linear')
|
|
request = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PublicGetMarketPremiumIndex(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1728577213289,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "ETH-USD",
|
|
# "lastFundingRate": "0.0001",
|
|
# "markPrice": "2402.68",
|
|
# "indexPrice": "2404.92",
|
|
# "nextFundingTime": 1728604800000
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PublicGetQuotePremiumIndex(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "symbol": "ETH-USDT",
|
|
# "markPrice": "2408.40",
|
|
# "indexPrice": "2409.62",
|
|
# "lastFundingRate": "0.00009900",
|
|
# "nextFundingTime": 1728604800000
|
|
# }
|
|
# }
|
|
#
|
|
if isinstance(response['data'], list):
|
|
return self.parse_ticker(self.safe_dict(response['data'], 0, {}), market)
|
|
return self.parse_ticker(response['data'], market)
|
|
|
|
def fetch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
fetches mark prices for multiple markets
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/market-api.html#Mark%20Price%20and%20Funding%20Rate
|
|
|
|
:param str[] [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()
|
|
market = None
|
|
if symbols is not None:
|
|
symbols = self.market_symbols(symbols)
|
|
firstSymbol = self.safe_string(symbols, 0)
|
|
if firstSymbol is not None:
|
|
market = self.market(firstSymbol)
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('fetchMarkPrices', market, params, 'linear')
|
|
response = None
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PublicGetMarketPremiumIndex(params)
|
|
else:
|
|
response = self.swapV2PublicGetQuotePremiumIndex(params)
|
|
#
|
|
# spot and swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720647285296,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "priceChange": "-2.418",
|
|
# "priceChangePercent": "-1.6900%",
|
|
# "lastPrice": "140.574",
|
|
# "lastQty": "1",
|
|
# "highPrice": "146.190",
|
|
# "lowPrice": "138.586",
|
|
# "volume": "1464648.00",
|
|
# "quoteVolume": "102928.12",
|
|
# "openPrice": "142.994",
|
|
# "closeTime": "1720647284976",
|
|
# "bidPrice": "140.573",
|
|
# "bidQty": "372",
|
|
# "askPrice": "140.577",
|
|
# "askQty": "58"
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
tickers = self.safe_list(response, 'data')
|
|
return self.parse_tickers(tickers, symbols)
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# mark price
|
|
# {
|
|
# "symbol": "string",
|
|
# "lastFundingRate": "string",
|
|
# "markPrice": "string",
|
|
# "indexPrice": "string",
|
|
# "nextFundingTime": "int64"
|
|
# }
|
|
#
|
|
# spot
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "openPrice": "26032.08",
|
|
# "highPrice": "26178.86",
|
|
# "lowPrice": "25968.18",
|
|
# "lastPrice": "26113.60",
|
|
# "volume": "1161.79",
|
|
# "quoteVolume": "30288466.44",
|
|
# "openTime": "1693081020762",
|
|
# "closeTime": "1693167420762",
|
|
# added 2023-11-10:
|
|
# "bidPrice": 16726.0,
|
|
# "bidQty": 0.05,
|
|
# "askPrice": 16726.0,
|
|
# "askQty": 0.05,
|
|
# }
|
|
# swap
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "priceChange": "52.5",
|
|
# "priceChangePercent": "0.31%", # they started to add the percent sign in value
|
|
# "lastPrice": "16880.5",
|
|
# "lastQty": "2.2238", # only present in swap!
|
|
# "highPrice": "16897.5",
|
|
# "lowPrice": "16726.0",
|
|
# "volume": "245870.1692",
|
|
# "quoteVolume": "4151395117.73",
|
|
# "openPrice": "16832.0",
|
|
# "openTime": 1672026667803,
|
|
# "closeTime": 1672026648425,
|
|
# added 2023-11-10:
|
|
# "bidPrice": 16726.0,
|
|
# "bidQty": 0.05,
|
|
# "askPrice": 16726.0,
|
|
# "askQty": 0.05,
|
|
# }
|
|
#
|
|
marketId = self.safe_string(ticker, 'symbol')
|
|
lastQty = self.safe_string(ticker, 'lastQty')
|
|
# in spot markets, lastQty is not present
|
|
# it's(bad, but) the only way we can check the tickers origin
|
|
type = 'spot' if (lastQty is None) else 'swap'
|
|
market = self.safe_market(marketId, market, None, type)
|
|
symbol = market['symbol']
|
|
open = self.safe_string(ticker, 'openPrice')
|
|
high = self.safe_string(ticker, 'highPrice')
|
|
low = self.safe_string(ticker, 'lowPrice')
|
|
close = self.safe_string(ticker, 'lastPrice')
|
|
quoteVolume = self.safe_string(ticker, 'quoteVolume')
|
|
baseVolume = self.safe_string(ticker, 'volume')
|
|
percentage = self.safe_string(ticker, 'priceChangePercent')
|
|
if percentage is not None:
|
|
percentage = percentage.replace('%', '')
|
|
change = self.safe_string(ticker, 'priceChange')
|
|
ts = self.safe_integer(ticker, 'closeTime')
|
|
if ts == 0:
|
|
ts = None
|
|
datetime = self.iso8601(ts)
|
|
bid = self.safe_string(ticker, 'bidPrice')
|
|
bidVolume = self.safe_string(ticker, 'bidQty')
|
|
ask = self.safe_string(ticker, 'askPrice')
|
|
askVolume = self.safe_string(ticker, 'askQty')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': ts,
|
|
'datetime': datetime,
|
|
'high': high,
|
|
'low': low,
|
|
'bid': bid,
|
|
'bidVolume': bidVolume,
|
|
'ask': ask,
|
|
'askVolume': askVolume,
|
|
'vwap': None,
|
|
'open': open,
|
|
'close': close,
|
|
'last': None,
|
|
'previousClose': None,
|
|
'change': change,
|
|
'percentage': percentage,
|
|
'average': None,
|
|
'baseVolume': baseVolume,
|
|
'quoteVolume': quoteVolume,
|
|
'markPrice': self.safe_string(ticker, 'markPrice'),
|
|
'indexPrice': self.safe_string(ticker, 'indexPrice'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://bingx-api.github.io/docs/#/spot/trade-api.html#Query%20Assets
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/account-api.html#Query%20account%20data
|
|
https://bingx-api.github.io/docs/#/standard/contract-interface.html#Query%20standard%20contract%20balance
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20Account%20Assets
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.standard]: whether to fetch standard contract balances
|
|
:param str [params.type]: the type of balance to fetch(spot, swap, funding) default is `spot`
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = None
|
|
standard = None
|
|
standard, params = self.handle_option_and_params(params, 'fetchBalance', 'standard', False)
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('fetchBalance', None, params)
|
|
marketType, marketTypeQuery = self.handle_market_type_and_params('fetchBalance', None, params)
|
|
if standard:
|
|
response = self.contractV1PrivateGetBalance(marketTypeQuery)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "timestamp": 1721192833454,
|
|
# "data": [
|
|
# {
|
|
# "asset": "USDT",
|
|
# "balance": "4.72644300000000000000",
|
|
# "crossWalletBalance": "4.72644300000000000000",
|
|
# "crossUnPnl": "0",
|
|
# "availableBalance": "4.72644300000000000000",
|
|
# "maxWithdrawAmount": "4.72644300000000000000",
|
|
# "marginAvailable": False,
|
|
# "updateTime": 1721192833443
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
elif (marketType == 'funding') or (marketType == 'fund'):
|
|
response = self.fundV1PrivateGetAccountBalance(marketTypeQuery)
|
|
# {
|
|
# code: '0',
|
|
# timestamp: '1754906016631',
|
|
# data: {
|
|
# assets: [
|
|
# {
|
|
# asset: 'USDT',
|
|
# free: '44.37692200000000237300',
|
|
# locked: '0.00000000000000000000'
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
elif marketType == 'spot':
|
|
response = self.spotV1PrivateGetAccountBalance(marketTypeQuery)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "balances": [
|
|
# {
|
|
# "asset": "USDT",
|
|
# "free": "45.733046995800514",
|
|
# "locked": "0"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PrivateGetUserBalance(marketTypeQuery)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1721191833813,
|
|
# "data": [
|
|
# {
|
|
# "asset": "SOL",
|
|
# "balance": "0.35707951",
|
|
# "equity": "0.35791051",
|
|
# "unrealizedProfit": "0.00083099",
|
|
# "availableMargin": "0.35160653",
|
|
# "usedMargin": "0.00630397",
|
|
# "freezedMargin": "0",
|
|
# "shortUid": "12851936"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV3PrivateGetUserBalance(marketTypeQuery)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": [
|
|
# {
|
|
# "userId": "116***295",
|
|
# "asset": "USDT",
|
|
# "balance": "194.8212",
|
|
# "equity": "196.7431",
|
|
# "unrealizedProfit": "1.9219",
|
|
# "realisedProfit": "-109.2504",
|
|
# "availableMargin": "193.7609",
|
|
# "usedMargin": "1.0602",
|
|
# "freezedMargin": "0.0000",
|
|
# "shortUid": "12851936"
|
|
# }
|
|
# ]
|
|
# }
|
|
return self.parse_balance(response)
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
#
|
|
# standard
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "timestamp": 1721192833454,
|
|
# "data": [
|
|
# {
|
|
# "asset": "USDT",
|
|
# "balance": "4.72644300000000000000",
|
|
# "crossWalletBalance": "4.72644300000000000000",
|
|
# "crossUnPnl": "0",
|
|
# "availableBalance": "4.72644300000000000000",
|
|
# "maxWithdrawAmount": "4.72644300000000000000",
|
|
# "marginAvailable": False,
|
|
# "updateTime": 1721192833443
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "balances": [
|
|
# {
|
|
# "asset": "USDT",
|
|
# "free": "45.733046995800514",
|
|
# "locked": "0"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1721191833813,
|
|
# "data": [
|
|
# {
|
|
# "asset": "SOL",
|
|
# "balance": "0.35707951",
|
|
# "equity": "0.35791051",
|
|
# "unrealizedProfit": "0.00083099",
|
|
# "availableMargin": "0.35160653",
|
|
# "usedMargin": "0.00630397",
|
|
# "freezedMargin": "0",
|
|
# "shortUid": "12851936"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": [
|
|
# {
|
|
# "userId": "116***295",
|
|
# "asset": "USDT",
|
|
# "balance": "194.8212",
|
|
# "equity": "196.7431",
|
|
# "unrealizedProfit": "1.9219",
|
|
# "realisedProfit": "-109.2504",
|
|
# "availableMargin": "193.7609",
|
|
# "usedMargin": "1.0602",
|
|
# "freezedMargin": "0.0000",
|
|
# "shortUid": "12851936"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result: dict = {'info': response}
|
|
contractBalances = self.safe_list(response, 'data')
|
|
firstContractBalances = self.safe_dict(contractBalances, 0)
|
|
isContract = firstContractBalances is not None
|
|
spotData = self.safe_dict(response, 'data', {})
|
|
spotBalances = self.safe_list_2(spotData, 'balances', 'assets', [])
|
|
if isContract:
|
|
for i in range(0, len(contractBalances)):
|
|
balance = contractBalances[i]
|
|
currencyId = self.safe_string(balance, 'asset')
|
|
if currencyId is None: # linear v3 returns empty asset
|
|
break
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string_2(balance, 'availableMargin', 'availableBalance')
|
|
account['used'] = self.safe_string(balance, 'usedMargin')
|
|
account['total'] = self.safe_string(balance, 'maxWithdrawAmount')
|
|
result[code] = account
|
|
else:
|
|
for i in range(0, len(spotBalances)):
|
|
balance = spotBalances[i]
|
|
currencyId = self.safe_string(balance, 'asset')
|
|
code = self.safe_currency_code(currencyId)
|
|
account = self.account()
|
|
account['free'] = self.safe_string(balance, 'free')
|
|
account['used'] = self.safe_string(balance, 'locked')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def fetch_position_history(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Position]:
|
|
"""
|
|
fetches historical positions
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20Position%20History
|
|
|
|
:param str symbol: unified contract symbol
|
|
:param int [since]: the earliest time in ms to fetch positions for
|
|
:param int [limit]: the maximum amount of records to fetch
|
|
:param dict [params]: extra parameters specific to the exchange api endpoint
|
|
:param int [params.until]: the latest time in ms to fetch positions for
|
|
:returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['pageSize'] = limit
|
|
if since is not None:
|
|
request['startTs'] = since
|
|
request, params = self.handle_until_option('endTs', request, params)
|
|
response = None
|
|
if market['linear']:
|
|
response = self.swapV1PrivateGetTradePositionHistory(self.extend(request, params))
|
|
else:
|
|
raise NotSupported(self.id + ' fetchPositionHistory() is not supported for inverse swap positions')
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "positionHistory": [
|
|
# {
|
|
# "positionId": "1861675561156571136",
|
|
# "symbol": "LTC-USDT",
|
|
# "isolated": False,
|
|
# "positionSide": "LONG",
|
|
# "openTime": 1732693017000,
|
|
# "updateTime": 1733310292000,
|
|
# "avgPrice": "95.18",
|
|
# "avgClosePrice": "129.48",
|
|
# "realisedProfit": "102.89",
|
|
# "netProfit": "99.63",
|
|
# "positionAmt": "30.0",
|
|
# "closePositionAmt": "30.0",
|
|
# "leverage": 6,
|
|
# "closeAllPositions": True,
|
|
# "positionCommission": "-0.33699650000000003",
|
|
# "totalFunding": "-2.921461693902908"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
records = self.safe_list(data, 'positionHistory', [])
|
|
positions = self.parse_positions(records)
|
|
return self.filter_by_symbol_since_limit(positions, symbol, since, limit)
|
|
|
|
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/account-api.html#Query%20position%20data
|
|
https://bingx-api.github.io/docs/#/en-us/standard/contract-interface.html#position
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20warehouse
|
|
|
|
:param str[]|None symbols: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param boolean [params.standard]: whether to fetch standard contract positions
|
|
:returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
standard = None
|
|
standard, params = self.handle_option_and_params(params, 'fetchPositions', 'standard', False)
|
|
response = None
|
|
if standard:
|
|
response = self.contractV1PrivateGetAllPosition(params)
|
|
else:
|
|
market = None
|
|
if symbols is not None:
|
|
symbols = self.market_symbols(symbols)
|
|
firstSymbol = self.safe_string(symbols, 0)
|
|
if firstSymbol is not None:
|
|
market = self.market(firstSymbol)
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('fetchPositions', market, params)
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PrivateGetUserPositions(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 0,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "positionId": "1813080351385337856",
|
|
# "positionSide": "LONG",
|
|
# "isolated": False,
|
|
# "positionAmt": "1",
|
|
# "availableAmt": "1",
|
|
# "unrealizedProfit": "-0.00009074",
|
|
# "initialMargin": "0.00630398",
|
|
# "liquidationPrice": 23.968303426677032,
|
|
# "avgPrice": "158.63",
|
|
# "leverage": 10,
|
|
# "markPrice": "158.402",
|
|
# "riskRate": "0.00123783",
|
|
# "maxMarginReduction": "0",
|
|
# "updateTime": 1721107015848
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivateGetUserPositions(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": [
|
|
# {
|
|
# "positionId": "1792480725958881280",
|
|
# "symbol": "LTC-USDT",
|
|
# "currency": "USDT",
|
|
# "positionAmt": "0.1",
|
|
# "availableAmt": "0.1",
|
|
# "positionSide": "LONG",
|
|
# "isolated": False,
|
|
# "avgPrice": "83.53",
|
|
# "initialMargin": "1.3922",
|
|
# "margin": "0.3528",
|
|
# "leverage": 6,
|
|
# "unrealizedProfit": "-1.0393",
|
|
# "realisedProfit": "-0.2119",
|
|
# "liquidationPrice": 0,
|
|
# "pnlRatio": "-0.7465",
|
|
# "maxMarginReduction": "0.0000",
|
|
# "riskRate": "0.0008",
|
|
# "markPrice": "73.14",
|
|
# "positionValue": "7.3136",
|
|
# "onlyOnePosition": True,
|
|
# "updateTime": 1721088016688
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
positions = self.safe_list(response, 'data', [])
|
|
return self.parse_positions(positions, symbols)
|
|
|
|
def fetch_position(self, symbol: str, params={}):
|
|
"""
|
|
fetch data on a single open contract trade position
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/account-api.html#Query%20position%20data
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20warehouse
|
|
|
|
: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)
|
|
if not market['swap']:
|
|
raise BadRequest(self.id + ' fetchPosition() supports swap markets only')
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
if market['inverse']:
|
|
response = self.cswapV1PrivateGetUserPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 0,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "positionId": "1813080351385337856",
|
|
# "positionSide": "LONG",
|
|
# "isolated": False,
|
|
# "positionAmt": "1",
|
|
# "availableAmt": "1",
|
|
# "unrealizedProfit": "-0.00009074",
|
|
# "initialMargin": "0.00630398",
|
|
# "liquidationPrice": 23.968303426677032,
|
|
# "avgPrice": "158.63",
|
|
# "leverage": 10,
|
|
# "markPrice": "158.402",
|
|
# "riskRate": "0.00123783",
|
|
# "maxMarginReduction": "0",
|
|
# "updateTime": 1721107015848
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivateGetUserPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": [
|
|
# {
|
|
# "positionId": "1792480725958881280",
|
|
# "symbol": "LTC-USDT",
|
|
# "currency": "USDT",
|
|
# "positionAmt": "0.1",
|
|
# "availableAmt": "0.1",
|
|
# "positionSide": "LONG",
|
|
# "isolated": False,
|
|
# "avgPrice": "83.53",
|
|
# "initialMargin": "1.3922",
|
|
# "margin": "0.3528",
|
|
# "leverage": 6,
|
|
# "unrealizedProfit": "-1.0393",
|
|
# "realisedProfit": "-0.2119",
|
|
# "liquidationPrice": 0,
|
|
# "pnlRatio": "-0.7465",
|
|
# "maxMarginReduction": "0.0000",
|
|
# "riskRate": "0.0008",
|
|
# "markPrice": "73.14",
|
|
# "positionValue": "7.3136",
|
|
# "onlyOnePosition": True,
|
|
# "updateTime": 1721088016688
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
first = self.safe_dict(data, 0, {})
|
|
return self.parse_position(first, market)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "positionId": "1813080351385337856",
|
|
# "positionSide": "LONG",
|
|
# "isolated": False,
|
|
# "positionAmt": "1",
|
|
# "availableAmt": "1",
|
|
# "unrealizedProfit": "-0.00009074",
|
|
# "initialMargin": "0.00630398",
|
|
# "liquidationPrice": 23.968303426677032,
|
|
# "avgPrice": "158.63",
|
|
# "leverage": 10,
|
|
# "markPrice": "158.402",
|
|
# "riskRate": "0.00123783",
|
|
# "maxMarginReduction": "0",
|
|
# "updateTime": 1721107015848
|
|
# }
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "positionId": "1792480725958881280",
|
|
# "symbol": "LTC-USDT",
|
|
# "currency": "USDT",
|
|
# "positionAmt": "0.1",
|
|
# "availableAmt": "0.1",
|
|
# "positionSide": "LONG",
|
|
# "isolated": False,
|
|
# "avgPrice": "83.53",
|
|
# "initialMargin": "1.3922",
|
|
# "margin": "0.3528",
|
|
# "leverage": 6,
|
|
# "unrealizedProfit": "-1.0393",
|
|
# "realisedProfit": "-0.2119",
|
|
# "liquidationPrice": 0,
|
|
# "pnlRatio": "-0.7465",
|
|
# "maxMarginReduction": "0.0000",
|
|
# "riskRate": "0.0008",
|
|
# "markPrice": "73.14",
|
|
# "positionValue": "7.3136",
|
|
# "onlyOnePosition": True,
|
|
# "updateTime": 1721088016688
|
|
# }
|
|
#
|
|
# standard position
|
|
#
|
|
# {
|
|
# "currentPrice": "82.91",
|
|
# "symbol": "LTC/USDT",
|
|
# "initialMargin": "5.00000000000000000000",
|
|
# "unrealizedProfit": "-0.26464500",
|
|
# "leverage": "20.000000000",
|
|
# "isolated": True,
|
|
# "entryPrice": "83.13",
|
|
# "positionSide": "LONG",
|
|
# "positionAmt": "1.20365912",
|
|
# }
|
|
#
|
|
# linear swap fetchPositionHistory
|
|
#
|
|
# {
|
|
# "positionId": "1861675561156571136",
|
|
# "symbol": "LTC-USDT",
|
|
# "isolated": False,
|
|
# "positionSide": "LONG",
|
|
# "openTime": 1732693017000,
|
|
# "updateTime": 1733310292000,
|
|
# "avgPrice": "95.18",
|
|
# "avgClosePrice": "129.48",
|
|
# "realisedProfit": "102.89",
|
|
# "netProfit": "99.63",
|
|
# "positionAmt": "30.0",
|
|
# "closePositionAmt": "30.0",
|
|
# "leverage": 6,
|
|
# "closeAllPositions": True,
|
|
# "positionCommission": "-0.33699650000000003",
|
|
# "totalFunding": "-2.921461693902908"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(position, 'symbol', '')
|
|
marketId = marketId.replace('/', '-') # standard return different format
|
|
isolated = self.safe_bool(position, 'isolated')
|
|
marginMode = None
|
|
if isolated is not None:
|
|
marginMode = 'isolated' if isolated else 'cross'
|
|
timestamp = self.safe_integer(position, 'openTime')
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': self.safe_string(position, 'positionId'),
|
|
'symbol': self.safe_symbol(marketId, market, '-', 'swap'),
|
|
'notional': self.safe_number(position, 'positionValue'),
|
|
'marginMode': marginMode,
|
|
'liquidationPrice': None,
|
|
'entryPrice': self.safe_number_2(position, 'avgPrice', 'entryPrice'),
|
|
'unrealizedPnl': self.safe_number(position, 'unrealizedProfit'),
|
|
'realizedPnl': self.safe_number(position, 'realisedProfit'),
|
|
'percentage': None,
|
|
'contracts': self.safe_number(position, 'positionAmt'),
|
|
'contractSize': None,
|
|
'markPrice': self.safe_number(position, 'markPrice'),
|
|
'lastPrice': None,
|
|
'side': self.safe_string_lower(position, 'positionSide'),
|
|
'hedged': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastUpdateTimestamp': self.safe_integer(position, 'updateTime'),
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'collateral': None,
|
|
'initialMargin': self.safe_number(position, 'initialMargin'),
|
|
'initialMarginPercentage': None,
|
|
'leverage': self.safe_number(position, 'leverage'),
|
|
'marginRatio': None,
|
|
'stopLossPrice': None,
|
|
'takeProfitPrice': None,
|
|
})
|
|
|
|
def create_market_order_with_cost(self, symbol: str, side: OrderSide, cost: float, params={}):
|
|
"""
|
|
create a market order by providing the symbol, side and cost
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str side: 'buy' or 'sell'
|
|
: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>`
|
|
"""
|
|
params['quoteOrderQty'] = cost
|
|
return self.create_order(symbol, 'market', side, cost, None, params)
|
|
|
|
def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
|
|
"""
|
|
create a market buy order by providing the symbol and cost
|
|
: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>`
|
|
"""
|
|
params['quoteOrderQty'] = cost
|
|
return self.create_order(symbol, 'market', 'buy', cost, None, params)
|
|
|
|
def create_market_sell_order_with_cost(self, symbol: str, cost: float, params={}):
|
|
"""
|
|
create a market sell order by providing the symbol and cost
|
|
: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>`
|
|
"""
|
|
params['quoteOrderQty'] = cost
|
|
return self.create_order(symbol, 'market', 'sell', cost, None, params)
|
|
|
|
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
@ignore
|
|
helper function to build request
|
|
: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 you want to trade in units of the 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
|
|
:returns dict: request to be sent to the exchange
|
|
"""
|
|
market = self.market(symbol)
|
|
postOnly = None
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('createOrder', market, params)
|
|
type = type.upper()
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'type': type,
|
|
'side': side.upper(),
|
|
}
|
|
isMarketOrder = type == 'MARKET'
|
|
isSpot = marketType == 'spot'
|
|
isTwapOrder = type == 'TWAP'
|
|
if isTwapOrder and isSpot:
|
|
raise BadSymbol(self.id + ' createOrder() twap order supports swap contracts only')
|
|
stopLossPrice = self.safe_string(params, 'stopLossPrice')
|
|
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
|
|
triggerPrice = self.safe_string_2(params, 'stopPrice', 'triggerPrice')
|
|
isTriggerOrder = triggerPrice is not None
|
|
isStopLossPriceOrder = stopLossPrice is not None
|
|
isTakeProfitPriceOrder = takeProfitPrice is not None
|
|
exchangeClientOrderId = 'newClientOrderId' if isSpot else 'clientOrderID'
|
|
clientOrderId = self.safe_string_2(params, exchangeClientOrderId, 'clientOrderId')
|
|
if clientOrderId is not None:
|
|
request[exchangeClientOrderId] = clientOrderId
|
|
timeInForce = self.safe_string_upper(params, 'timeInForce')
|
|
postOnly, params = self.handle_post_only(isMarketOrder, timeInForce == 'PostOnly', params)
|
|
if postOnly or (timeInForce == 'PostOnly'):
|
|
request['timeInForce'] = 'PostOnly'
|
|
elif timeInForce == 'IOC':
|
|
request['timeInForce'] = 'IOC'
|
|
elif timeInForce == 'GTC':
|
|
request['timeInForce'] = 'GTC'
|
|
if isSpot:
|
|
cost = self.safe_string_2(params, 'cost', 'quoteOrderQty')
|
|
params = self.omit(params, 'cost')
|
|
if cost is not None:
|
|
request['quoteOrderQty'] = self.parse_to_numeric(self.cost_to_precision(symbol, cost))
|
|
else:
|
|
if isMarketOrder and (price is not None):
|
|
# keep the legacy behavior, to avoid breaking the old spot-market-buying code
|
|
calculatedCost = Precise.string_mul(self.number_to_string(amount), self.number_to_string(price))
|
|
request['quoteOrderQty'] = self.parse_to_numeric(calculatedCost)
|
|
else:
|
|
request['quantity'] = self.parse_to_numeric(self.amount_to_precision(symbol, amount))
|
|
if not isMarketOrder:
|
|
request['price'] = self.parse_to_numeric(self.price_to_precision(symbol, price))
|
|
if triggerPrice is not None:
|
|
if isMarketOrder and self.safe_string(request, 'quoteOrderQty') is None:
|
|
raise ArgumentsRequired(self.id + ' createOrder() requires the cost parameter(or the amount + price) for placing spot market-buy trigger orders')
|
|
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
|
|
if type == 'LIMIT':
|
|
request['type'] = 'TRIGGER_LIMIT'
|
|
elif type == 'MARKET':
|
|
request['type'] = 'TRIGGER_MARKET'
|
|
elif (stopLossPrice is not None) or (takeProfitPrice is not None):
|
|
stopTakePrice = stopLossPrice if (stopLossPrice is not None) else takeProfitPrice
|
|
if type == 'LIMIT':
|
|
request['type'] = 'TAKE_STOP_LIMIT'
|
|
elif type == 'MARKET':
|
|
request['type'] = 'TAKE_STOP_MARKET'
|
|
request['stopPrice'] = self.parse_to_numeric(self.price_to_precision(symbol, stopTakePrice))
|
|
else:
|
|
if isTwapOrder:
|
|
twapRequest: dict = {
|
|
'symbol': request['symbol'],
|
|
'side': request['side'],
|
|
'positionSide': 'LONG' if (side == 'buy') else 'SHORT',
|
|
'triggerPrice': self.parse_to_numeric(self.price_to_precision(symbol, triggerPrice)),
|
|
'totalAmount': self.parse_to_numeric(self.amount_to_precision(symbol, amount)),
|
|
}
|
|
# {
|
|
# "symbol": "LTC-USDT",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "priceType": "constant",
|
|
# "priceVariance": "10",
|
|
# "triggerPrice": "120",
|
|
# "interval": 8,
|
|
# "amountPerOrder": "0.5",
|
|
# "totalAmount": "1"
|
|
# }
|
|
return self.extend(twapRequest, params)
|
|
if timeInForce == 'FOK':
|
|
request['timeInForce'] = 'FOK'
|
|
trailingAmount = self.safe_string(params, 'trailingAmount')
|
|
trailingPercent = self.safe_string_2(params, 'trailingPercent', 'priceRate')
|
|
trailingType = self.safe_string(params, 'trailingType', 'TRAILING_STOP_MARKET')
|
|
isTrailingAmountOrder = trailingAmount is not None
|
|
isTrailingPercentOrder = trailingPercent is not None
|
|
isTrailing = isTrailingAmountOrder or isTrailingPercentOrder
|
|
stopLoss = self.safe_value(params, 'stopLoss')
|
|
takeProfit = self.safe_value(params, 'takeProfit')
|
|
isStopLoss = stopLoss is not None
|
|
isTakeProfit = takeProfit is not None
|
|
if ((type == 'LIMIT') or (type == 'TRIGGER_LIMIT') or (type == 'STOP') or (type == 'TAKE_PROFIT')) and not isTrailing:
|
|
request['price'] = self.parse_to_numeric(self.price_to_precision(symbol, price))
|
|
reduceOnly = self.safe_bool(params, 'reduceOnly', False)
|
|
if isTriggerOrder:
|
|
request['stopPrice'] = self.parse_to_numeric(self.price_to_precision(symbol, triggerPrice))
|
|
if isMarketOrder or (type == 'TRIGGER_MARKET'):
|
|
request['type'] = 'TRIGGER_MARKET'
|
|
elif (type == 'LIMIT') or (type == 'TRIGGER_LIMIT'):
|
|
request['type'] = 'TRIGGER_LIMIT'
|
|
elif isStopLossPriceOrder or isTakeProfitPriceOrder:
|
|
# This can be used to set the stop loss and take profit, but the position needs to be opened first
|
|
reduceOnly = True
|
|
if isStopLossPriceOrder:
|
|
request['stopPrice'] = self.parse_to_numeric(self.price_to_precision(symbol, stopLossPrice))
|
|
if isMarketOrder or (type == 'STOP_MARKET'):
|
|
request['type'] = 'STOP_MARKET'
|
|
elif (type == 'LIMIT') or (type == 'STOP'):
|
|
request['type'] = 'STOP'
|
|
elif isTakeProfitPriceOrder:
|
|
request['stopPrice'] = self.parse_to_numeric(self.price_to_precision(symbol, takeProfitPrice))
|
|
if isMarketOrder or (type == 'TAKE_PROFIT_MARKET'):
|
|
request['type'] = 'TAKE_PROFIT_MARKET'
|
|
elif (type == 'LIMIT') or (type == 'TAKE_PROFIT'):
|
|
request['type'] = 'TAKE_PROFIT'
|
|
elif isTrailing:
|
|
request['type'] = trailingType
|
|
if isTrailingAmountOrder:
|
|
request['price'] = self.parse_to_numeric(trailingAmount)
|
|
elif isTrailingPercentOrder:
|
|
requestTrailingPercent = Precise.string_div(trailingPercent, '100')
|
|
request['priceRate'] = self.parse_to_numeric(requestTrailingPercent)
|
|
if isStopLoss or isTakeProfit:
|
|
stringifiedAmount = self.number_to_string(amount)
|
|
if isStopLoss:
|
|
slTriggerPrice = self.safe_string_2(stopLoss, 'triggerPrice', 'stopPrice', stopLoss)
|
|
slWorkingType = self.safe_string(stopLoss, 'workingType', 'MARK_PRICE')
|
|
slType = self.safe_string(stopLoss, 'type', 'STOP_MARKET')
|
|
slRequest: dict = {
|
|
'stopPrice': self.parse_to_numeric(self.price_to_precision(symbol, slTriggerPrice)),
|
|
'workingType': slWorkingType,
|
|
'type': slType,
|
|
}
|
|
slPrice = self.safe_string(stopLoss, 'price')
|
|
if slPrice is not None:
|
|
slRequest['price'] = self.parse_to_numeric(self.price_to_precision(symbol, slPrice))
|
|
slQuantity = self.safe_string(stopLoss, 'quantity', stringifiedAmount)
|
|
slRequest['quantity'] = self.parse_to_numeric(self.amount_to_precision(symbol, slQuantity))
|
|
request['stopLoss'] = self.json(slRequest)
|
|
if isTakeProfit:
|
|
tkTriggerPrice = self.safe_string_2(takeProfit, 'triggerPrice', 'stopPrice', takeProfit)
|
|
tkWorkingType = self.safe_string(takeProfit, 'workingType', 'MARK_PRICE')
|
|
tpType = self.safe_string(takeProfit, 'type', 'TAKE_PROFIT_MARKET')
|
|
tpRequest: dict = {
|
|
'stopPrice': self.parse_to_numeric(self.price_to_precision(symbol, tkTriggerPrice)),
|
|
'workingType': tkWorkingType,
|
|
'type': tpType,
|
|
}
|
|
slPrice = self.safe_string(takeProfit, 'price')
|
|
if slPrice is not None:
|
|
tpRequest['price'] = self.parse_to_numeric(self.price_to_precision(symbol, slPrice))
|
|
tkQuantity = self.safe_string(takeProfit, 'quantity', stringifiedAmount)
|
|
tpRequest['quantity'] = self.parse_to_numeric(self.amount_to_precision(symbol, tkQuantity))
|
|
request['takeProfit'] = self.json(tpRequest)
|
|
positionSide = None
|
|
hedged = self.safe_bool(params, 'hedged', False)
|
|
if hedged:
|
|
params = self.omit(params, 'reduceOnly')
|
|
if reduceOnly:
|
|
positionSide = 'SHORT' if (side == 'buy') else 'LONG'
|
|
else:
|
|
positionSide = 'LONG' if (side == 'buy') else 'SHORT'
|
|
else:
|
|
positionSide = 'BOTH'
|
|
request['positionSide'] = positionSide
|
|
amountReq = amount
|
|
if not market['inverse']:
|
|
amountReq = self.parse_to_numeric(self.amount_to_precision(symbol, amount))
|
|
request['quantity'] = amountReq # precision not available for inverse contracts
|
|
params = self.omit(params, ['hedged', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'trailingAmount', 'trailingPercent', 'trailingType', 'takeProfit', 'stopLoss', 'clientOrderId'])
|
|
return self.extend(request, params)
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Trade%20order
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Create%20an%20Order
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Trade%20order
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Place%20TWAP%20Order
|
|
|
|
: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 you want to trade in units of the 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.clientOrderId]: a unique id for the order
|
|
:param bool [params.postOnly]: True to place a post only order
|
|
:param str [params.timeInForce]: spot supports 'PO', 'GTC' and 'IOC', swap supports 'PO', 'GTC', 'IOC' and 'FOK'
|
|
:param bool [params.reduceOnly]: *swap only* True or False whether the order is reduce only
|
|
:param float [params.triggerPrice]: triggerPrice at which the attached take profit / stop loss order will be triggered
|
|
:param float [params.stopLossPrice]: stop loss trigger price
|
|
:param float [params.takeProfitPrice]: take profit trigger price
|
|
:param float [params.cost]: the quote quantity that can be used alternative for the amount
|
|
:param float [params.trailingAmount]: *swap only* the quote amount to trail away from the current market price
|
|
:param float [params.trailingPercent]: *swap only* the percent to trail away from the current market price
|
|
:param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered
|
|
:param float [params.takeProfit.triggerPrice]: take profit trigger price
|
|
:param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered
|
|
:param float [params.stopLoss.triggerPrice]: stop loss trigger price
|
|
:param boolean [params.test]: *swap only* whether to use the test endpoint or not, default is False
|
|
:param str [params.positionSide]: *contracts only* "BOTH" for one way mode, "LONG" for buy side of hedged mode, "SHORT" for sell side of hedged mode
|
|
:param boolean [params.hedged]: *swap only* whether the order is in hedged mode or one way mode
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
test = self.safe_bool(params, 'test', False)
|
|
params = self.omit(params, 'test')
|
|
request = self.create_order_request(symbol, type, side, amount, price, params)
|
|
response = None
|
|
if market['swap']:
|
|
if test:
|
|
response = self.swapV2PrivatePostTradeOrderTest(request)
|
|
elif market['inverse']:
|
|
response = self.cswapV1PrivatePostTradeOrder(request)
|
|
elif type == 'twap':
|
|
response = self.swapV1PrivatePostTwapOrder(request)
|
|
else:
|
|
response = self.swapV2PrivatePostTradeOrder(request)
|
|
else:
|
|
response = self.spotV1PrivatePostTradeOrder(request)
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "symbol": "XRP-USDT",
|
|
# "orderId": 1514090846268424192,
|
|
# "transactTime": 1649822362855,
|
|
# "price": "0.5",
|
|
# "origQty": "10",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "PENDING",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY"
|
|
# }
|
|
# }
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "order": {
|
|
# "symbol": "BTC-USDT",
|
|
# "orderId": 1709036527545438208,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "TRIGGER_LIMIT",
|
|
# "clientOrderID": "",
|
|
# "workingType": ""
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "orderId": 1809841379603398656,
|
|
# "symbol": "SOL-USD",
|
|
# "positionSide": "LONG",
|
|
# "side": "BUY",
|
|
# "type": "LIMIT",
|
|
# "price": 100,
|
|
# "quantity": 1,
|
|
# "stopPrice": 0,
|
|
# "workingType": "",
|
|
# "timeInForce": ""
|
|
# }
|
|
#
|
|
# twap order
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1732693774386,
|
|
# "data": {
|
|
# "mainOrderId": "4633860139993029715"
|
|
# }
|
|
# }
|
|
#
|
|
if isinstance(response, str):
|
|
# broken api engine : order-ids are too long numbers(i.e. 1742930526912864656)
|
|
# and json.loadscan not handle them in JS, so we have to use .parseJson
|
|
# however, when order has an attached SL/TP, their value types need extra parsing
|
|
response = self.fix_stringified_json_members(response)
|
|
response = self.parse_json(response)
|
|
data = self.safe_dict(response, 'data', {})
|
|
result: dict = {}
|
|
if market['swap']:
|
|
if market['inverse']:
|
|
result = response
|
|
else:
|
|
result = self.safe_dict(data, 'order', data)
|
|
else:
|
|
result = data
|
|
return self.parse_order(result, market)
|
|
|
|
def create_orders(self, orders: List[OrderRequest], params={}):
|
|
"""
|
|
create a list of trade orders
|
|
|
|
https://bingx-api.github.io/docs/#/spot/trade-api.html#Batch%20Placing%20Orders
|
|
https://bingx-api.github.io/docs/#/swapV2/trade-api.html#Bulk%20order
|
|
|
|
: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 boolean [params.sync]: *spot only* if True, multiple orders are ordered serially and all orders do not require the same symbol/side/type
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
ordersRequests = []
|
|
marketIds = []
|
|
for i in range(0, len(orders)):
|
|
rawOrder = orders[i]
|
|
marketId = self.safe_string(rawOrder, 'symbol')
|
|
type = self.safe_string(rawOrder, 'type')
|
|
marketIds.append(marketId)
|
|
side = self.safe_string(rawOrder, 'side')
|
|
amount = self.safe_number(rawOrder, 'amount')
|
|
price = self.safe_number(rawOrder, 'price')
|
|
orderParams = self.safe_dict(rawOrder, 'params', {})
|
|
orderRequest = self.create_order_request(marketId, type, side, amount, price, orderParams)
|
|
ordersRequests.append(orderRequest)
|
|
symbols = self.market_symbols(marketIds, None, False, True, True)
|
|
symbolsLength = len(symbols)
|
|
market = self.market(symbols[0])
|
|
request: dict = {}
|
|
response = None
|
|
if market['swap']:
|
|
if symbolsLength > 5:
|
|
raise InvalidOrder(self.id + ' createOrders() can not create more than 5 orders at once for swap markets')
|
|
request['batchOrders'] = self.json(ordersRequests)
|
|
response = self.swapV2PrivatePostTradeBatchOrders(request)
|
|
else:
|
|
sync = self.safe_bool(params, 'sync', False)
|
|
if sync:
|
|
request['sync'] = True
|
|
request['data'] = self.json(ordersRequests)
|
|
response = self.spotV1PrivatePostTradeBatchOrders(request)
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "orderId": 1720661389564968960,
|
|
# "transactTime": 1699072618272,
|
|
# "price": "25000",
|
|
# "origQty": "0.0002",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "PENDING",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "orderId": 1720657081994006528,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "LIMIT",
|
|
# "clientOrderID": "",
|
|
# "workingType": ""
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
if isinstance(response, str):
|
|
# broken api engine : order-ids are too long numbers(i.e. 1742930526912864656)
|
|
# and json.loadscan not handle them in JS, so we have to use .parseJson
|
|
# however, when order has an attached SL/TP, their value types need extra parsing
|
|
response = self.fix_stringified_json_members(response)
|
|
response = self.parse_json(response)
|
|
data = self.safe_dict(response, 'data', {})
|
|
result = self.safe_list(data, 'orders', [])
|
|
return self.parse_orders(result, market)
|
|
|
|
def parse_order_side(self, side):
|
|
sides: dict = {
|
|
'BUY': 'buy',
|
|
'SELL': 'sell',
|
|
'SHORT': 'sell',
|
|
'LONG': 'buy',
|
|
'ask': 'sell',
|
|
'bid': 'buy',
|
|
}
|
|
return self.safe_string(sides, side, side)
|
|
|
|
def parse_order_type(self, type: Str):
|
|
types: dict = {
|
|
'trigger_market': 'market',
|
|
'trigger_limit': 'limit',
|
|
'stop_limit': 'limit',
|
|
'stop_market': 'market',
|
|
'take_profit_market': 'market',
|
|
'stop': 'limit',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# spot
|
|
# createOrder, createOrders, cancelOrder
|
|
#
|
|
# {
|
|
# "symbol": "XRP-USDT",
|
|
# "orderId": 1514090846268424192,
|
|
# "transactTime": 1649822362855,
|
|
# "price": "0.5",
|
|
# "origQty": "10",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "PENDING",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY"
|
|
# }
|
|
#
|
|
# fetchOrder
|
|
#
|
|
# {
|
|
# "symbol": "ETH-USDT",
|
|
# "orderId": "1660602123001266176",
|
|
# "price": "1700",
|
|
# "origQty": "0.003",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "PENDING",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "time": "1684753373276",
|
|
# "updateTime": "1684753373276",
|
|
# "origQuoteOrderQty": "0",
|
|
# "fee": "0",
|
|
# "feeAsset": "ETH"
|
|
# }
|
|
#
|
|
# fetchOpenOrders, fetchClosedOrders
|
|
#
|
|
# {
|
|
# "symbol": "XRP-USDT",
|
|
# "orderId": 1514073325788200960,
|
|
# "price": "0.5",
|
|
# "StopPrice": "0",
|
|
# "origQty": "20",
|
|
# "executedQty": "10",
|
|
# "cummulativeQuoteQty": "5",
|
|
# "status": "PENDING",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "time": 1649818185647,
|
|
# "updateTime": 1649818185647,
|
|
# "origQuoteOrderQty": "0"
|
|
# "fee": "-0.01"
|
|
# }
|
|
#
|
|
#
|
|
# linear swap
|
|
# createOrder, createOrders
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "orderId": 1590973236294713344,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "LIMIT"
|
|
# }
|
|
#
|
|
# inverse swap createOrder
|
|
#
|
|
# {
|
|
# "orderId": 1809841379603398656,
|
|
# "symbol": "SOL-USD",
|
|
# "positionSide": "LONG",
|
|
# "side": "BUY",
|
|
# "type": "LIMIT",
|
|
# "price": 100,
|
|
# "quantity": 1,
|
|
# "stopPrice": 0,
|
|
# "workingType": "",
|
|
# "timeInForce": ""
|
|
# }
|
|
#
|
|
# fetchOrder, fetchOpenOrders, fetchClosedOrders
|
|
#
|
|
# {
|
|
# "symbol": "BTC-USDT",
|
|
# "orderId": 1709036527545438208,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "TRIGGER_LIMIT",
|
|
# "origQty": "0.0010",
|
|
# "price": "22000.0",
|
|
# "executedQty": "0.0000",
|
|
# "avgPrice": "0.0",
|
|
# "cumQuote": "",
|
|
# "stopPrice": "23000.0",
|
|
# "profit": "",
|
|
# "commission": "",
|
|
# "status": "NEW",
|
|
# "time": 1696301035187,
|
|
# "updateTime": 1696301035187,
|
|
# "clientOrderId": "",
|
|
# "leverage": "",
|
|
# "takeProfit": "",
|
|
# "stopLoss": "",
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": "MARK_PRICE"
|
|
# }
|
|
# with tp and sl
|
|
# {
|
|
# orderId: 1741440894764281900,
|
|
# symbol: 'LTC-USDT',
|
|
# positionSide: 'LONG',
|
|
# side: 'BUY',
|
|
# type: 'MARKET',
|
|
# price: 0,
|
|
# quantity: 1,
|
|
# stopPrice: 0,
|
|
# workingType: 'MARK_PRICE',
|
|
# clientOrderID: '',
|
|
# timeInForce: 'GTC',
|
|
# priceRate: 0,
|
|
# stopLoss: '{"stopPrice":50,"workingType":"MARK_PRICE","type":"STOP_MARKET","quantity":1}',
|
|
# takeProfit: '{"stopPrice":150,"workingType":"MARK_PRICE","type":"TAKE_PROFIT_MARKET","quantity":1}',
|
|
# reduceOnly: False
|
|
# }
|
|
#
|
|
# editOrder(swap)
|
|
#
|
|
# {
|
|
# cancelResult: 'true',
|
|
# cancelMsg: '',
|
|
# cancelResponse: {
|
|
# cancelClientOrderId: '',
|
|
# cancelOrderId: '1755336244265705472',
|
|
# symbol: 'SOL-USDT',
|
|
# orderId: '1755336244265705472',
|
|
# side: 'SELL',
|
|
# positionSide: 'SHORT',
|
|
# type: 'LIMIT',
|
|
# origQty: '1',
|
|
# price: '100.000',
|
|
# executedQty: '0',
|
|
# avgPrice: '0.000',
|
|
# cumQuote: '0',
|
|
# stopPrice: '',
|
|
# profit: '0.0000',
|
|
# commission: '0.000000',
|
|
# status: 'PENDING',
|
|
# time: '1707339747860',
|
|
# updateTime: '1707339747860',
|
|
# clientOrderId: '',
|
|
# leverage: '20X',
|
|
# workingType: 'MARK_PRICE',
|
|
# onlyOnePosition: False,
|
|
# reduceOnly: False
|
|
# },
|
|
# replaceResult: 'true',
|
|
# replaceMsg: '',
|
|
# newOrderResponse: {
|
|
# orderId: '1755338440612995072',
|
|
# symbol: 'SOL-USDT',
|
|
# positionSide: 'SHORT',
|
|
# side: 'SELL',
|
|
# type: 'LIMIT',
|
|
# price: '99',
|
|
# quantity: '2',
|
|
# stopPrice: '0',
|
|
# workingType: 'MARK_PRICE',
|
|
# clientOrderID: '',
|
|
# timeInForce: 'GTC',
|
|
# priceRate: '0',
|
|
# stopLoss: '',
|
|
# takeProfit: '',
|
|
# reduceOnly: False
|
|
# }
|
|
# }
|
|
#
|
|
# editOrder(spot)
|
|
#
|
|
# {
|
|
# cancelResult: {code: '0', msg: '', result: True},
|
|
# openResult: {code: '0', msg: '', result: True},
|
|
# orderOpenResponse: {
|
|
# symbol: 'SOL-USDT',
|
|
# orderId: '1755334007697866752',
|
|
# transactTime: '1707339214620',
|
|
# price: '99',
|
|
# stopPrice: '0',
|
|
# origQty: '0.2',
|
|
# executedQty: '0',
|
|
# cummulativeQuoteQty: '0',
|
|
# status: 'PENDING',
|
|
# type: 'LIMIT',
|
|
# side: 'SELL',
|
|
# clientOrderID: ''
|
|
# },
|
|
# orderCancelResponse: {
|
|
# symbol: 'SOL-USDT',
|
|
# orderId: '1755117055251480576',
|
|
# price: '100',
|
|
# stopPrice: '0',
|
|
# origQty: '0.2',
|
|
# executedQty: '0',
|
|
# cummulativeQuoteQty: '0',
|
|
# status: 'CANCELED',
|
|
# type: 'LIMIT',
|
|
# side: 'SELL'
|
|
# }
|
|
# }
|
|
#
|
|
# stop loss order
|
|
#
|
|
# {
|
|
# "symbol": "ETH-USDT",
|
|
# "orderId": "1792461744476422144",
|
|
# "price": "2775.65",
|
|
# "StopPrice": "2778.42",
|
|
# "origQty": "0.032359",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "NEW",
|
|
# "type": "TAKE_STOP_LIMIT",
|
|
# "side": "SELL",
|
|
# "time": "1716191156868",
|
|
# "updateTime": "1716191156868",
|
|
# "origQuoteOrderQty": "0",
|
|
# "fee": "0",
|
|
# "feeAsset": "USDT",
|
|
# "clientOrderID": ""
|
|
# }
|
|
#
|
|
# inverse swap cancelAllOrders, cancelOrder, fetchOrder, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders
|
|
#
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "orderId": "1809845251327672320",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "LIMIT",
|
|
# "quantity": 1,
|
|
# "origQty": "0",
|
|
# "price": "90",
|
|
# "executedQty": "0",
|
|
# "avgPrice": "0",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.000000",
|
|
# "status": "CANCELLED",
|
|
# "time": 1720335707872,
|
|
# "updateTime": 1720335707912,
|
|
# "clientOrderId": "",
|
|
# "leverage": "",
|
|
# "takeProfit": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "stopLoss": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": ""
|
|
# }
|
|
#
|
|
info = order
|
|
newOrder = self.safe_dict_2(order, 'newOrderResponse', 'orderOpenResponse')
|
|
if newOrder is not None:
|
|
order = newOrder
|
|
positionSide = self.safe_string_2(order, 'positionSide', 'ps')
|
|
marketType = 'spot' if (positionSide is None) else 'swap'
|
|
marketId = self.safe_string_2(order, 'symbol', 's')
|
|
if market is None:
|
|
market = self.safe_market(marketId, None, None, marketType)
|
|
side = self.safe_string_lower_2(order, 'side', 'S')
|
|
timestamp = self.safe_integer_n(order, ['time', 'transactTime', 'E', 'createdTime'])
|
|
lastTradeTimestamp = self.safe_integer_2(order, 'updateTime', 'T')
|
|
statusId = self.safe_string_upper_n(order, ['status', 'X', 'orderStatus'])
|
|
feeCurrencyCode = self.safe_string_2(order, 'feeAsset', 'N')
|
|
feeCost = self.safe_string_n(order, ['fee', 'commission', 'n'])
|
|
if (feeCurrencyCode is None):
|
|
if market['spot']:
|
|
if side == 'buy':
|
|
feeCurrencyCode = market['base']
|
|
else:
|
|
feeCurrencyCode = market['quote']
|
|
else:
|
|
feeCurrencyCode = market['quote']
|
|
stopLoss = self.safe_value(order, 'stopLoss')
|
|
stopLossPrice = None
|
|
if (stopLoss is not None) and (stopLoss != ''):
|
|
stopLossPrice = self.omit_zero(self.safe_string(stopLoss, 'stopLoss'))
|
|
if (stopLoss is not None) and ((not isinstance(stopLoss, numbers.Real))) and (stopLoss != ''):
|
|
# stopLoss: '{"stopPrice":50,"workingType":"MARK_PRICE","type":"STOP_MARKET","quantity":1}',
|
|
if isinstance(stopLoss, str):
|
|
stopLoss = self.parse_json(stopLoss)
|
|
stopLossPrice = self.omit_zero(self.safe_string(stopLoss, 'stopPrice'))
|
|
takeProfit = self.safe_value(order, 'takeProfit')
|
|
takeProfitPrice = None
|
|
if takeProfit is not None and (takeProfit != ''):
|
|
takeProfitPrice = self.omit_zero(self.safe_string(takeProfit, 'takeProfit'))
|
|
if (takeProfit is not None) and ((not isinstance(takeProfit, numbers.Real))) and (takeProfit != ''):
|
|
# takeProfit: '{"stopPrice":150,"workingType":"MARK_PRICE","type":"TAKE_PROFIT_MARKET","quantity":1}',
|
|
if isinstance(takeProfit, str):
|
|
takeProfit = self.parse_json(takeProfit)
|
|
takeProfitPrice = self.omit_zero(self.safe_string(takeProfit, 'stopPrice'))
|
|
rawType = self.safe_string_lower_2(order, 'type', 'o')
|
|
stopPrice = self.omit_zero(self.safe_string_2(order, 'StopPrice', 'stopPrice'))
|
|
triggerPrice = stopPrice
|
|
if stopPrice is not None:
|
|
if (rawType.find('stop') > -1) and (stopLossPrice is None):
|
|
stopLossPrice = stopPrice
|
|
triggerPrice = None
|
|
if (rawType.find('take') > -1) and (takeProfitPrice is None):
|
|
takeProfitPrice = stopPrice
|
|
triggerPrice = None
|
|
return self.safe_order({
|
|
'info': info,
|
|
'id': self.safe_string_n(order, ['orderId', 'i', 'mainOrderId']),
|
|
'clientOrderId': self.safe_string_n(order, ['clientOrderID', 'clientOrderId', 'origClientOrderId', 'c']),
|
|
'symbol': self.safe_symbol(marketId, market, '-', marketType),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': lastTradeTimestamp,
|
|
'lastUpdateTimestamp': self.safe_integer(order, 'updateTime'),
|
|
'type': self.parse_order_type(rawType),
|
|
'timeInForce': self.safe_string(order, 'timeInForce'),
|
|
'postOnly': None,
|
|
'side': self.parse_order_side(side),
|
|
'price': self.safe_string_2(order, 'price', 'p'),
|
|
'triggerPrice': triggerPrice,
|
|
'stopLossPrice': stopLossPrice,
|
|
'takeProfitPrice': takeProfitPrice,
|
|
'average': self.safe_string_2(order, 'avgPrice', 'ap'),
|
|
'cost': self.safe_string(order, 'cummulativeQuoteQty'),
|
|
'amount': self.safe_string_n(order, ['origQty', 'q', 'quantity', 'totalAmount']),
|
|
'filled': self.safe_string_2(order, 'executedQty', 'z'),
|
|
'remaining': None,
|
|
'status': self.parse_order_status(statusId),
|
|
'fee': {
|
|
'currency': feeCurrencyCode,
|
|
'cost': Precise.string_abs(feeCost),
|
|
},
|
|
'trades': None,
|
|
'reduceOnly': self.safe_bool_2(order, 'reduceOnly', 'ro'),
|
|
}, market)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'NEW': 'open',
|
|
'PENDING': 'open',
|
|
'PARTIALLY_FILLED': 'open',
|
|
'RUNNING': 'open',
|
|
'FILLED': 'closed',
|
|
'CANCELED': 'canceled',
|
|
'CANCELLED': 'canceled',
|
|
'FAILED': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Cancel%20Order
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Cancel%20Order
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Cancel%20an%20Order
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Cancel%20TWAP%20Order
|
|
|
|
: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]: a unique id for the order
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
isTwapOrder = self.safe_bool(params, 'twap', False)
|
|
params = self.omit(params, 'twap')
|
|
response = None
|
|
market = None
|
|
if isTwapOrder:
|
|
twapRequest: dict = {
|
|
'mainOrderId': id,
|
|
}
|
|
response = self.swapV1PrivatePostTwapCancelOrder(self.extend(twapRequest, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1702731661854,
|
|
# "data": {
|
|
# "symbol": "BNB-USDT",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "priceType": "constant",
|
|
# "priceVariance": "2000",
|
|
# "triggerPrice": "68000",
|
|
# "interval": 8,
|
|
# "amountPerOrder": "0.111",
|
|
# "totalAmount": "0.511",
|
|
# "orderStatus": "Running",
|
|
# "executedQty": "0.1",
|
|
# "duration": 800,
|
|
# "maxDuration": 9000,
|
|
# "createdTime": 1702731661854,
|
|
# "updateTime": 1702731661854
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clientOrderID')
|
|
params = self.omit(params, ['clientOrderId'])
|
|
if clientOrderId is not None:
|
|
request['clientOrderID'] = clientOrderId
|
|
else:
|
|
request['orderId'] = id
|
|
type = None
|
|
subType = None
|
|
type, params = self.handle_market_type_and_params('cancelOrder', market, params)
|
|
subType, params = self.handle_sub_type_and_params('cancelOrder', market, params)
|
|
if type == 'spot':
|
|
response = self.spotV1PrivatePostTradeCancel(self.extend(request, params))
|
|
else:
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PrivateDeleteTradeCancelOrder(self.extend(request, params))
|
|
else:
|
|
response = self.swapV2PrivateDeleteTradeOrder(self.extend(request, params))
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "symbol": "XRP-USDT",
|
|
# "orderId": 1514090846268424192,
|
|
# "price": "0.5",
|
|
# "origQty": "10",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "CANCELED",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY"
|
|
# }
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "order": {
|
|
# "symbol": "SOL-USD",
|
|
# "orderId": "1816002957423951872",
|
|
# "side": "BUY",
|
|
# "positionSide": "Long",
|
|
# "type": "Pending",
|
|
# "quantity": 0,
|
|
# "origQty": "0",
|
|
# "price": "150",
|
|
# "executedQty": "0",
|
|
# "avgPrice": "0",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.000000",
|
|
# "status": "CANCELLED",
|
|
# "time": 1721803819410,
|
|
# "updateTime": 1721803819427,
|
|
# "clientOrderId": "",
|
|
# "leverage": "",
|
|
# "takeProfit": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "stopLoss": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": ""
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "order": {
|
|
# "symbol": "LINK-USDT",
|
|
# "orderId": 1597783850786750464,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "TRIGGER_MARKET",
|
|
# "origQty": "5.0",
|
|
# "price": "5.0000",
|
|
# "executedQty": "0.0",
|
|
# "avgPrice": "0.0000",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "5.0000",
|
|
# "profit": "",
|
|
# "commission": "",
|
|
# "status": "CANCELLED",
|
|
# "time": 1669776330000,
|
|
# "updateTime": 1669776330000
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
order = self.safe_dict(data, 'order', data)
|
|
return self.parse_order(order, market)
|
|
|
|
def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Cancel%20orders%20by%20symbol
|
|
https://bingx-api.github.io/docs/#/swapV2/trade-api.html#Cancel%20All%20Orders
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Cancel%20all%20orders
|
|
|
|
:param str [symbol]: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not 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>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
if market['spot']:
|
|
response = self.spotV1PrivatePostTradeCancelOpenOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "orders": [{
|
|
# "symbol": "ADA-USDT",
|
|
# "orderId": 1740659971369992192,
|
|
# "transactTime": 1703840651730,
|
|
# "price": 5,
|
|
# "stopPrice": 0,
|
|
# "origQty": 10,
|
|
# "executedQty": 0,
|
|
# "cummulativeQuoteQty": 0,
|
|
# "status": "CANCELED",
|
|
# "type": "LIMIT",
|
|
# "side": "SELL"
|
|
# }]
|
|
# }
|
|
# }
|
|
#
|
|
elif market['swap']:
|
|
if market['inverse']:
|
|
response = self.cswapV1PrivateDeleteTradeAllOpenOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720501468364,
|
|
# "data": {
|
|
# "success": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "orderId": "1809845251327672320",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "LIMIT",
|
|
# "quantity": 1,
|
|
# "origQty": "0",
|
|
# "price": "90",
|
|
# "executedQty": "0",
|
|
# "avgPrice": "0",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.000000",
|
|
# "status": "CANCELLED",
|
|
# "time": 1720335707872,
|
|
# "updateTime": 1720335707912,
|
|
# "clientOrderId": "",
|
|
# "leverage": "",
|
|
# "takeProfit": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "stopLoss": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": ""
|
|
# }
|
|
# ],
|
|
# "failed": null
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivateDeleteTradeAllOpenOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "success": [
|
|
# {
|
|
# "symbol": "LINK-USDT",
|
|
# "orderId": 1597783835095859200,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "TRIGGER_LIMIT",
|
|
# "origQty": "5.0",
|
|
# "price": "9.0000",
|
|
# "executedQty": "0.0",
|
|
# "avgPrice": "0.0000",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "9.5000",
|
|
# "profit": "",
|
|
# "commission": "",
|
|
# "status": "NEW",
|
|
# "time": 1669776326000,
|
|
# "updateTime": 1669776326000
|
|
# }
|
|
# ],
|
|
# "failed": null
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
raise BadRequest(self.id + ' cancelAllOrders is only supported for spot and swap markets.')
|
|
data = self.safe_dict(response, 'data', {})
|
|
orders = self.safe_list_2(data, 'success', 'orders', [])
|
|
return self.parse_orders(orders)
|
|
|
|
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
|
"""
|
|
cancel multiple orders
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/trade-api.html#Cancel%20a%20Batch%20of%20Orders
|
|
https://bingx-api.github.io/docs/#/spot/trade-api.html#Cancel%20a%20Batch%20of%20Orders
|
|
|
|
:param str[] ids: order ids
|
|
:param str symbol: unified market symbol, default is None
|
|
: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)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
clientOrderIds = self.safe_value(params, 'clientOrderIds')
|
|
params = self.omit(params, 'clientOrderIds')
|
|
idsToParse = ids
|
|
areClientOrderIds = (clientOrderIds is not None)
|
|
if areClientOrderIds:
|
|
idsToParse = clientOrderIds
|
|
parsedIds = []
|
|
for i in range(0, len(idsToParse)):
|
|
id = idsToParse[i]
|
|
stringId = str(id)
|
|
parsedIds.append(stringId)
|
|
response = None
|
|
if market['spot']:
|
|
spotReqKey = 'clientOrderIDs' if areClientOrderIds else 'orderIds'
|
|
request[spotReqKey] = ','.join(parsedIds)
|
|
response = self.spotV1PrivatePostTradeCancelOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "SOL-USDT",
|
|
# "orderId": 1795970045910614016,
|
|
# "transactTime": 1717027601111,
|
|
# "price": "180.25",
|
|
# "stopPrice": "0",
|
|
# "origQty": "0.03",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "CANCELED",
|
|
# "type": "LIMIT",
|
|
# "side": "SELL",
|
|
# "clientOrderID": ""
|
|
# },
|
|
# ...
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
if areClientOrderIds:
|
|
request['clientOrderIDList'] = self.json(parsedIds)
|
|
else:
|
|
request['orderIdList'] = parsedIds
|
|
response = self.swapV2PrivateDeleteTradeBatchOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "success": [
|
|
# {
|
|
# "symbol": "LINK-USDT",
|
|
# "orderId": 1597783850786750464,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "TRIGGER_MARKET",
|
|
# "origQty": "5.0",
|
|
# "price": "5.5710",
|
|
# "executedQty": "0.0",
|
|
# "avgPrice": "0.0000",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "5.0000",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.000000",
|
|
# "status": "CANCELLED",
|
|
# "time": 1669776330000,
|
|
# "updateTime": 1672370837000
|
|
# }
|
|
# ],
|
|
# "failed": null
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
success = self.safe_list_2(data, 'success', 'orders', [])
|
|
return self.parse_orders(success)
|
|
|
|
def cancel_all_orders_after(self, timeout: Int, params={}):
|
|
"""
|
|
dead man's switch, cancel all orders after the given timeout
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Cancel%20all%20orders%20in%20countdown
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Cancel%20all%20orders%20in%20countdown
|
|
|
|
:param number timeout: time in milliseconds, 0 represents cancel the timer
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.type]: spot or swap market
|
|
:returns dict: the api result
|
|
"""
|
|
self.load_markets()
|
|
isActive = (timeout > 0)
|
|
request: dict = {
|
|
'type': 'ACTIVATE' if (isActive) else 'CLOSE',
|
|
'timeOut': (self.parse_to_int(timeout / 1000)) if (isActive) else 0,
|
|
}
|
|
response = None
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('cancelAllOrdersAfter', None, params)
|
|
if type == 'spot':
|
|
response = self.spotV1PrivatePostTradeCancelAllAfter(self.extend(request, params))
|
|
elif type == 'swap':
|
|
response = self.swapV2PrivatePostTradeCancelAllAfter(self.extend(request, params))
|
|
else:
|
|
raise NotSupported(self.id + ' cancelAllOrdersAfter() is not supported for ' + type + ' markets')
|
|
#
|
|
# {
|
|
# code: '0',
|
|
# msg: '',
|
|
# data: {
|
|
# triggerTime: '1712645434',
|
|
# status: 'ACTIVATED',
|
|
# note: 'All your perpetual pending orders will be closed automatically at 2024-04-09 06:50:34 UTC(+0),before that you can cancel the timer, or self.extend triggerTime time by self request'
|
|
# }
|
|
# }
|
|
#
|
|
return response
|
|
|
|
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Query%20Order%20details
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20Order%20details
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20Order
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#TWAP%20Order%20Details
|
|
|
|
:param str id: the 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 boolean [params.twap]: if fetching twap order
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
isTwapOrder = self.safe_bool(params, 'twap', False)
|
|
params = self.omit(params, 'twap')
|
|
response = None
|
|
market = None
|
|
if isTwapOrder:
|
|
twapRequest: dict = {
|
|
'mainOrderId': id,
|
|
}
|
|
response = self.swapV1PrivateGetTwapOrderDetail(self.extend(twapRequest, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "success cancel order",
|
|
# "timestamp": 1732760856617,
|
|
# "data": {
|
|
# "symbol": "LTC-USDT",
|
|
# "mainOrderId": "5596903086063901779",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "priceType": "constant",
|
|
# "priceVariance": "10.00",
|
|
# "triggerPrice": "120.00",
|
|
# "interval": 8,
|
|
# "amountPerOrder": "0.5",
|
|
# "totalAmount": "1.0",
|
|
# "orderStatus": "Filled",
|
|
# "executedQty": "1.0",
|
|
# "duration": 16,
|
|
# "maxDuration": 86400,
|
|
# "createdTime": 1732693017000,
|
|
# "updateTime": 1732693033000
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'orderId': id,
|
|
}
|
|
type = None
|
|
subType = None
|
|
type, params = self.handle_market_type_and_params('fetchOrder', market, params)
|
|
subType, params = self.handle_sub_type_and_params('fetchOrder', market, params)
|
|
if type == 'spot':
|
|
response = self.spotV1PrivateGetTradeQuery(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "symbol": "XRP-USDT",
|
|
# "orderId": 1514087361158316032,
|
|
# "price": "0.5",
|
|
# "origQty": "10",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "CANCELED",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "time": 1649821532000,
|
|
# "updateTime": 1649821543000,
|
|
# "origQuoteOrderQty": "0",
|
|
# "fee": "0",
|
|
# "feeAsset": "XRP"
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PrivateGetTradeOrderDetail(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "order": {
|
|
# "symbol": "SOL-USD",
|
|
# "orderId": "1816342420721254400",
|
|
# "side": "BUY",
|
|
# "positionSide": "Long",
|
|
# "type": "LIMIT",
|
|
# "quantity": 1,
|
|
# "origQty": "",
|
|
# "price": "150",
|
|
# "executedQty": "0",
|
|
# "avgPrice": "0.000",
|
|
# "cumQuote": "",
|
|
# "stopPrice": "",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.0000",
|
|
# "status": "Pending",
|
|
# "time": 1721884753767,
|
|
# "updateTime": 1721884753786,
|
|
# "clientOrderId": "",
|
|
# "leverage": "",
|
|
# "takeProfit": {
|
|
# "type": "TAKE_PROFIT",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "MARK_PRICE",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "stopLoss": {
|
|
# "type": "STOP",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "MARK_PRICE",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": "MARK_PRICE"
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivateGetTradeOrder(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "order": {
|
|
# "symbol": "BTC-USDT",
|
|
# "orderId": 1597597642269917184,
|
|
# "side": "SELL",
|
|
# "positionSide": "LONG",
|
|
# "type": "TAKE_PROFIT_MARKET",
|
|
# "origQty": "1.0000",
|
|
# "price": "0.0",
|
|
# "executedQty": "0.0000",
|
|
# "avgPrice": "0.0",
|
|
# "cumQuote": "",
|
|
# "stopPrice": "16494.0",
|
|
# "profit": "",
|
|
# "commission": "",
|
|
# "status": "FILLED",
|
|
# "time": 1669731935000,
|
|
# "updateTime": 1669752524000
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
order = self.safe_dict(data, 'order', data)
|
|
return self.parse_order(order, market)
|
|
|
|
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple orders made by the user
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#All%20Orders
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20Order%20history(returns less fields than above)
|
|
|
|
: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]: the latest time in ms to fetch entries for
|
|
:param int [params.orderId]: Only return subsequent orders, and return the latest order by default
|
|
:returns Order[]: 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']
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('fetchOrders', market, params)
|
|
if type != 'swap':
|
|
raise NotSupported(self.id + ' fetchOrders() is only supported for swap markets')
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
response = self.swapV1PrivateGetTradeFullOrder(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "PYTH-USDT",
|
|
# "orderId": 1736007506620112100,
|
|
# "side": "SELL",
|
|
# "positionSide": "SHORT",
|
|
# "type": "LIMIT",
|
|
# "origQty": "33",
|
|
# "price": "0.3916",
|
|
# "executedQty": "33",
|
|
# "avgPrice": "0.3916",
|
|
# "cumQuote": "13",
|
|
# "stopPrice": "",
|
|
# "profit": "0.0000",
|
|
# "commission": "-0.002585",
|
|
# "status": "FILLED",
|
|
# "time": 1702731418000,
|
|
# "updateTime": 1702731470000,
|
|
# "clientOrderId": "",
|
|
# "leverage": "15X",
|
|
# "takeProfit": {
|
|
# "type": "TAKE_PROFIT",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": ""
|
|
# },
|
|
# "stopLoss": {
|
|
# "type": "STOP",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": ""
|
|
# },
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": "MARK_PRICE",
|
|
# "stopGuaranteed": False,
|
|
# "triggerOrderId": 1736012449498123500
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
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]:
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Current%20Open%20Orders
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Current%20All%20Open%20Orders
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20all%20current%20pending%20orders
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20TWAP%20Entrusted%20Order
|
|
|
|
: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.twap]: if fetching twap open orders
|
|
:returns dict[]: 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
|
|
subType = None
|
|
response = None
|
|
type, params = self.handle_market_type_and_params('fetchOpenOrders', market, params)
|
|
subType, params = self.handle_sub_type_and_params('fetchOpenOrders', market, params)
|
|
if type == 'spot':
|
|
response = self.spotV1PrivateGetTradeOpenOrders(self.extend(request, params))
|
|
else:
|
|
isTwapOrder = self.safe_bool(params, 'twap', False)
|
|
params = self.omit(params, 'twap')
|
|
if isTwapOrder:
|
|
response = self.swapV1PrivateGetTwapOpenOrders(self.extend(request, params))
|
|
elif subType == 'inverse':
|
|
response = self.cswapV1PrivateGetTradeOpenOrders(self.extend(request, params))
|
|
else:
|
|
response = self.swapV2PrivateGetTradeOpenOrders(self.extend(request, params))
|
|
#
|
|
# spot
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "XRP-USDT",
|
|
# "orderId": 1514073325788200960,
|
|
# "price": "0.5",
|
|
# "origQty": "20",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "PENDING",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "time": 1649818185647,
|
|
# "updateTime": 1649818185647,
|
|
# "origQuoteOrderQty": "0"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "orderId": "1816013900044320768",
|
|
# "side": "BUY",
|
|
# "positionSide": "Long",
|
|
# "type": "LIMIT",
|
|
# "quantity": 1,
|
|
# "origQty": "",
|
|
# "price": "150",
|
|
# "executedQty": "0",
|
|
# "avgPrice": "0.000",
|
|
# "cumQuote": "",
|
|
# "stopPrice": "",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.0000",
|
|
# "status": "Pending",
|
|
# "time": 1721806428334,
|
|
# "updateTime": 1721806428352,
|
|
# "clientOrderId": "",
|
|
# "leverage": "",
|
|
# "takeProfit": {
|
|
# "type": "TAKE_PROFIT",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "MARK_PRICE",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "stopLoss": {
|
|
# "type": "STOP",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "MARK_PRICE",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": "MARK_PRICE"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "LINK-USDT",
|
|
# "orderId": 1585839271162413056,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "TRIGGER_MARKET",
|
|
# "origQty": "5.0",
|
|
# "price": "9",
|
|
# "executedQty": "0.0",
|
|
# "avgPrice": "0",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "5",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.000000",
|
|
# "status": "CANCELLED",
|
|
# "time": 1667631605000,
|
|
# "updateTime": 1667631605000
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
# twap
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1702731661854,
|
|
# "data": {
|
|
# "list": [
|
|
# {
|
|
# "symbol": "BNB-USDT",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "priceType": "constant",
|
|
# "priceVariance": "2000",
|
|
# "triggerPrice": "68000",
|
|
# "interval": 8,
|
|
# "amountPerOrder": "0.111",
|
|
# "totalAmount": "0.511",
|
|
# "orderStatus": "Running",
|
|
# "executedQty": "0.1",
|
|
# "duration": 800,
|
|
# "maxDuration": 9000,
|
|
# "createdTime": 1702731661854,
|
|
# "updateTime": 1702731661854
|
|
# }
|
|
# ],
|
|
# "total": 1
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
orders = self.safe_list_2(data, 'orders', 'list', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple closed orders made by the user
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Query%20Order%20history
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20Order%20history
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#User's%20History%20Orders
|
|
https://bingx-api.github.io/docs/#/standard/contract-interface.html#Historical%20order
|
|
|
|
:param str symbol: unified market symbol of the closed orders
|
|
:param int [since]: timestamp in ms of the earliest order
|
|
:param int [limit]: the max number of closed orders to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch orders for
|
|
:param boolean [params.standard]: whether to fetch standard contract orders
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
orders = self.fetch_canceled_and_closed_orders(symbol, since, limit, params)
|
|
return self.filter_by(orders, 'status', 'closed')
|
|
|
|
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
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Query%20Order%20history
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20Order%20history
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#User's%20History%20Orders
|
|
https://bingx-api.github.io/docs/#/standard/contract-interface.html#Historical%20order
|
|
|
|
:param str symbol: unified market symbol of the canceled orders
|
|
:param int [since]: timestamp in ms of the earliest order
|
|
:param int [limit]: the max number of canceled orders to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch orders for
|
|
:param boolean [params.standard]: whether to fetch standard contract orders
|
|
:returns dict: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
orders = self.fetch_canceled_and_closed_orders(symbol, since, limit, params)
|
|
return self.filter_by(orders, 'status', 'canceled')
|
|
|
|
def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches information on multiple closed orders made by the user
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Query%20Order%20history
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20Order%20history
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#User's%20History%20Orders
|
|
https://bingx-api.github.io/docs/#/standard/contract-interface.html#Historical%20order
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20TWAP%20Historical%20Orders
|
|
|
|
: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]: the latest time in ms to fetch orders for
|
|
:param boolean [params.standard]: whether to fetch standard contract orders
|
|
:param boolean [params.twap]: if fetching twap orders
|
|
:returns dict[]: 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
|
|
subType = None
|
|
standard = None
|
|
response = None
|
|
type, params = self.handle_market_type_and_params('fetchClosedOrders', market, params)
|
|
subType, params = self.handle_sub_type_and_params('fetchClosedOrders', market, params)
|
|
standard, params = self.handle_option_and_params(params, 'fetchClosedOrders', 'standard', False)
|
|
if standard:
|
|
response = self.contractV1PrivateGetAllOrders(self.extend(request, params))
|
|
elif type == 'spot':
|
|
if limit is not None:
|
|
request['pageSize'] = limit
|
|
response = self.spotV1PrivateGetTradeHistoryOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "XRP-USDT",
|
|
# "orderId": 1514073325788200960,
|
|
# "price": "0.5",
|
|
# "origQty": "20",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "status": "PENDING",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "time": 1649818185647,
|
|
# "updateTime": 1649818185647,
|
|
# "origQuoteOrderQty": "0"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
isTwapOrder = self.safe_bool(params, 'twap', False)
|
|
params = self.omit(params, 'twap')
|
|
if isTwapOrder:
|
|
request['pageIndex'] = 1
|
|
request['pageSize'] = 100 if (limit is None) else limit
|
|
request['startTime'] = 1 if (since is None) else since
|
|
until = self.safe_integer(params, 'until', self.milliseconds())
|
|
params = self.omit(params, 'until')
|
|
request['endTime'] = until
|
|
response = self.swapV1PrivateGetTwapHistoryOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1702731661854,
|
|
# "data": {
|
|
# "list": [
|
|
# {
|
|
# "symbol": "BNB-USDT",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "priceType": "constant",
|
|
# "priceVariance": "2000",
|
|
# "triggerPrice": "68000",
|
|
# "interval": 8,
|
|
# "amountPerOrder": "0.111",
|
|
# "totalAmount": "0.511",
|
|
# "orderStatus": "Running",
|
|
# "executedQty": "0.1",
|
|
# "duration": 800,
|
|
# "maxDuration": 9000,
|
|
# "createdTime": 1702731661854,
|
|
# "updateTime": 1702731661854
|
|
# }
|
|
# ],
|
|
# "total": 1
|
|
# }
|
|
# }
|
|
#
|
|
elif subType == 'inverse':
|
|
response = self.cswapV1PrivateGetTradeOrderHistory(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "orderId": "1816002957423951872",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "LIMIT",
|
|
# "quantity": 1,
|
|
# "origQty": "10.00000000",
|
|
# "price": "150.000",
|
|
# "executedQty": "0.00000000",
|
|
# "avgPrice": "0.000",
|
|
# "cumQuote": "",
|
|
# "stopPrice": "0.000",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.000000",
|
|
# "status": "Filled",
|
|
# "time": 1721803819000,
|
|
# "updateTime": 1721803856000,
|
|
# "clientOrderId": "",
|
|
# "leverage": "",
|
|
# "takeProfit": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "stopLoss": {
|
|
# "type": "",
|
|
# "quantity": 0,
|
|
# "stopPrice": 0,
|
|
# "price": 0,
|
|
# "workingType": "",
|
|
# "stopGuaranteed": ""
|
|
# },
|
|
# "advanceAttr": 0,
|
|
# "positionID": 0,
|
|
# "takeProfitEntrustPrice": 0,
|
|
# "stopLossEntrustPrice": 0,
|
|
# "orderType": "",
|
|
# "workingType": "MARK_PRICE"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivateGetTradeAllOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "symbol": "LINK-USDT",
|
|
# "orderId": 1585839271162413056,
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "type": "TRIGGER_MARKET",
|
|
# "origQty": "5.0",
|
|
# "price": "9",
|
|
# "executedQty": "0.0",
|
|
# "avgPrice": "0",
|
|
# "cumQuote": "0",
|
|
# "stopPrice": "5",
|
|
# "profit": "0.0000",
|
|
# "commission": "0.000000",
|
|
# "status": "CANCELLED",
|
|
# "time": 1667631605000,
|
|
# "updateTime": 1667631605000
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
orders = self.safe_list_2(data, 'orders', 'list', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
|
"""
|
|
transfer currency internally between wallets on the same account
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/common/account-api.html#Asset%20Transfer%20New
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: amount to transfer
|
|
:param str fromAccount: account to transfer from(spot, swap, futures, or funding)
|
|
:param str toAccount: account to transfer to(spot, swap(linear or inverse), future, or funding)
|
|
: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)
|
|
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('transfer', None, params)
|
|
fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
|
|
toId = self.safe_string(accountsByType, toAccount, toAccount)
|
|
if fromId == 'swap':
|
|
if subType == 'inverse':
|
|
fromId = 'coinMPerp'
|
|
else:
|
|
fromId = 'USDTMPerp'
|
|
if toId == 'swap':
|
|
if subType == 'inverse':
|
|
toId = 'coinMPerp'
|
|
else:
|
|
toId = 'USDTMPerp'
|
|
request: dict = {
|
|
'fromAccount': fromId,
|
|
'toAccount': toId,
|
|
'asset': currency['id'],
|
|
'amount': self.currency_to_precision(code, amount),
|
|
}
|
|
response = self.apiAssetV1PrivatePostTransfer(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "tranId": 1933130865269936128,
|
|
# "transferId": "1051450703949464903736"
|
|
# }
|
|
#
|
|
return {
|
|
'info': response,
|
|
'id': self.safe_string(response, 'transferId'),
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'currency': code,
|
|
'amount': amount,
|
|
'fromAccount': fromAccount,
|
|
'toAccount': toAccount,
|
|
'status': None,
|
|
}
|
|
|
|
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
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/common/account-api.html#Asset%20transfer%20records%20new
|
|
|
|
:param str [code]: unified currency code of the currency transferred
|
|
:param int [since]: the earliest time in ms to fetch transfers for
|
|
:param int [limit]: the maximum number of transfers structures to retrieve(default 10, max 100)
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str params.fromAccount:(mandatory) transfer from(spot, swap(linear or inverse), future, or funding)
|
|
:param str params.toAccount:(mandatory) transfer to(spot, swap(linear or inverse), future, or funding)
|
|
:param boolean [params.paginate]: whether to paginate the results(default False)
|
|
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {}
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
|
|
fromAccount = self.safe_string(params, 'fromAccount')
|
|
toAccount = self.safe_string(params, 'toAccount')
|
|
fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
|
|
toId = self.safe_string(accountsByType, toAccount, toAccount)
|
|
if fromId is None or toId is None:
|
|
raise ExchangeError(self.id + ' fromAccount & toAccount parameters are required')
|
|
if fromAccount is not None:
|
|
request['fromAccount'] = fromId
|
|
if toAccount is not None:
|
|
request['toAccount'] = toId
|
|
params = self.omit(params, ['fromAccount', 'toAccount'])
|
|
maxLimit = 100
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchTransfers', 'paginate', False)
|
|
if paginate:
|
|
return self.fetch_paginated_call_dynamic('fetchTransfers', None, since, limit, params, maxLimit)
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['pageSize'] = limit
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
response = self.apiV3PrivateGetAssetTransferRecord(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "total": 2,
|
|
# "rows": [
|
|
# {
|
|
# "asset": "LTC",
|
|
# "amount": "0.05000000000000000000",
|
|
# "status": "CONFIRMED",
|
|
# "transferId": "1051461075661819338791",
|
|
# "timestamp": 1752202092000,
|
|
# "fromAccount": "spot",
|
|
# "toAccount": "USDTMPerp"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
rows = self.safe_list(response, 'rows', [])
|
|
return self.parse_transfers(rows, currency, since, limit)
|
|
|
|
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
|
tranId = self.safe_string(transfer, 'transferId')
|
|
timestamp = self.safe_integer(transfer, 'timestamp')
|
|
currencyId = self.safe_string(transfer, 'asset')
|
|
currencyCode = self.safe_currency_code(currencyId, currency)
|
|
status = self.safe_string(transfer, 'status')
|
|
accountsById = self.safe_dict(self.options, 'accountsById', {})
|
|
fromId = self.safe_string(transfer, 'fromAccount')
|
|
toId = self.safe_string(transfer, 'toAccount')
|
|
fromAccount = self.safe_string(accountsById, fromId, fromId)
|
|
toAccount = self.safe_string(accountsById, toId, toId)
|
|
return {
|
|
'info': transfer,
|
|
'id': tranId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'currency': currencyCode,
|
|
'amount': self.safe_number(transfer, 'amount'),
|
|
'fromAccount': fromAccount,
|
|
'toAccount': toAccount,
|
|
'status': self.parse_transfer_status(status),
|
|
}
|
|
|
|
def parse_transfer_status(self, status: Str) -> str:
|
|
statuses: dict = {
|
|
'CONFIRMED': 'ok',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def fetch_deposit_addresses_by_network(self, code: str, params={}) -> List[DepositAddress]:
|
|
"""
|
|
fetch the deposit addresses for a currency associated with self account
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/common/wallet-api.html#Query%20Main%20Account%20Deposit%20Address
|
|
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary `address structures <https://docs.ccxt.com/#/?id=address-structure>`, indexed by the network
|
|
"""
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
defaultRecvWindow = self.safe_integer(self.options, 'recvWindow')
|
|
recvWindow = self.safe_integer(self.parse_params, 'recvWindow', defaultRecvWindow)
|
|
request: dict = {
|
|
'coin': currency['id'],
|
|
'offset': 0,
|
|
'limit': 1000,
|
|
'recvWindow': recvWindow,
|
|
}
|
|
response = self.walletsV1PrivateGetCapitalDepositAddress(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": "0",
|
|
# "timestamp": "1695200226859",
|
|
# "data": {
|
|
# "data": [
|
|
# {
|
|
# "coinId": "799",
|
|
# "coin": "USDT",
|
|
# "network": "BEP20",
|
|
# "address": "6a7eda2817462dabb6493277a2cfe0f5c3f2550b",
|
|
# "tag": ''
|
|
# }
|
|
# ],
|
|
# "total": "1"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_list(self.safe_dict(response, 'data'), 'data')
|
|
parsed = self.parse_deposit_addresses(data, [currency['code']], False)
|
|
return self.index_by(parsed, 'network')
|
|
|
|
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/common/wallet-api.html#Query%20Main%20Account%20Deposit%20Address
|
|
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.network]: The chain of currency. This only apply for multi-chain currency, and there is no need for single chain currency
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
network = self.safe_string(params, 'network')
|
|
params = self.omit(params, ['network'])
|
|
addressStructures = self.fetch_deposit_addresses_by_network(code, params)
|
|
if network is not None:
|
|
return self.safe_dict(addressStructures, network)
|
|
else:
|
|
options = self.safe_dict(self.options, 'defaultNetworks')
|
|
defaultNetworkForCurrency = self.safe_string(options, code)
|
|
if defaultNetworkForCurrency is not None:
|
|
return self.safe_dict(addressStructures, defaultNetworkForCurrency)
|
|
else:
|
|
keys = list(addressStructures.keys())
|
|
key = self.safe_string(keys, 0)
|
|
return self.safe_dict(addressStructures, key)
|
|
|
|
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
|
#
|
|
# {
|
|
# "coinId":"4",
|
|
# "coin":"USDT",
|
|
# "network":"OMNI",
|
|
# "address":"1HXyx8HVQRY7Nhqz63nwnRB7SpS9xQPzLN",
|
|
# "addressWithPrefix":"1HXyx8HVQRY7Nhqz63nwnRB7SpS9xQPzLN"
|
|
# }
|
|
#
|
|
tag = self.safe_string(depositAddress, 'tag')
|
|
currencyId = self.safe_string(depositAddress, 'coin')
|
|
currency = self.safe_currency(currencyId, currency)
|
|
code = currency['code']
|
|
address = self.safe_string(depositAddress, 'addressWithPrefix')
|
|
networkdId = self.safe_string(depositAddress, 'network')
|
|
networkCode = self.network_id_to_code(networkdId, code)
|
|
self.check_address(address)
|
|
return {
|
|
'info': depositAddress,
|
|
'currency': code,
|
|
'network': networkCode,
|
|
'address': address,
|
|
'tag': tag,
|
|
}
|
|
|
|
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all deposits made to an account
|
|
|
|
https://bingx-api.github.io/docs/#/spot/account-api.html#Deposit%20History(supporting%20network)
|
|
|
|
: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>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
}
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['coin'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit # default 1000
|
|
response = self.spotV3PrivateGetCapitalDepositHisrec(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "amount":"0.00999800",
|
|
# "coin":"PAXG",
|
|
# "network":"ETH",
|
|
# "status":1,
|
|
# "address":"0x788cabe9236ce061e5a892e1a59395a81fc8d62c",
|
|
# "addressTag":"",
|
|
# "txId":"0xaad4654a3234aa6118af9b4b335f5ae81c360b2394721c019b5d1e75328b09f3",
|
|
# "insertTime":1599621997000,
|
|
# "transferType":0,
|
|
# "unlockConfirm":"12/12", # confirm times for unlocking
|
|
# "confirmTimes":"12/12"
|
|
# },
|
|
# ]
|
|
#
|
|
return self.parse_transactions(response, currency, since, limit)
|
|
|
|
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all withdrawals made from an account
|
|
|
|
https://bingx-api.github.io/docs/#/spot/account-api.html#Withdraw%20History%20(supporting%20network)
|
|
|
|
: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>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
}
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['coin'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit # default 1000
|
|
response = self.spotV3PrivateGetCapitalWithdrawHistory(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "address": "0x94df8b352de7f46f64b01d3666bf6e936e44ce60",
|
|
# "amount": "8.91000000",
|
|
# "applyTime": "2019-10-12 11:12:02",
|
|
# "coin": "USDT",
|
|
# "id": "b6ae22b3aa844210a7041aee7589627c",
|
|
# "withdrawOrderId": "WITHDRAWtest123",
|
|
# "network": "ETH",
|
|
# "transferType": 0
|
|
# "status": 6,
|
|
# "transactionFee": "0.004",
|
|
# "confirmNo":3,
|
|
# "info": "The address is not valid. Please confirm with the recipient",
|
|
# "txId": "0xb5ef8c13b968a406cc62a93a8bd80f9e9a906ef1b3fcf20a2e48573c17659268"
|
|
# },
|
|
# ]
|
|
#
|
|
return self.parse_transactions(response, currency, since, limit)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# fetchDeposits
|
|
#
|
|
# {
|
|
# "amount":"0.00999800",
|
|
# "coin":"PAXG",
|
|
# "network":"ETH",
|
|
# "status":1,
|
|
# "address":"0x788cabe9236ce061e5a892e1a59395a81fc8d62c",
|
|
# "addressTag":"",
|
|
# "txId":"0xaad4654a3234aa6118af9b4b335f5ae81c360b2394721c019b5d1e75328b09f3",
|
|
# "insertTime":1599621997000,
|
|
# "transferType":0,
|
|
# "unlockConfirm":"12/12", # confirm times for unlocking
|
|
# "confirmTimes":"12/12"
|
|
# }
|
|
#
|
|
# fetchWithdrawals
|
|
#
|
|
# {
|
|
# "address": "0x94df8b352de7f46f64b01d3666bf6e936e44ce60",
|
|
# "amount": "8.91000000",
|
|
# "applyTime": "2019-10-12 11:12:02",
|
|
# "coin": "USDT",
|
|
# "id": "b6ae22b3aa844210a7041aee7589627c",
|
|
# "withdrawOrderId": "WITHDRAWtest123",
|
|
# "network": "ETH",
|
|
# "transferType": 0
|
|
# "status": 6,
|
|
# "transactionFee": "0.004",
|
|
# "confirmNo":3,
|
|
# "info": "The address is not valid. Please confirm with the recipient",
|
|
# "txId": "0xb5ef8c13b968a406cc62a93a8bd80f9e9a906ef1b3fcf20a2e48573c17659268"
|
|
# }
|
|
#
|
|
# withdraw
|
|
#
|
|
# {
|
|
# "code":0,
|
|
# "timestamp":1705274263621,
|
|
# "data":{
|
|
# "id":"1264246141278773252"
|
|
# }
|
|
# }
|
|
#
|
|
# parse withdraw-type output first...
|
|
#
|
|
data = self.safe_value(transaction, 'data')
|
|
dataId = None if (data is None) else self.safe_string(data, 'id')
|
|
id = self.safe_string(transaction, 'id', dataId)
|
|
address = self.safe_string(transaction, 'address')
|
|
tag = self.safe_string(transaction, 'addressTag')
|
|
timestamp = self.safe_integer_2(transaction, 'insertTime', 'timestamp')
|
|
datetime = self.iso8601(timestamp)
|
|
if timestamp is None:
|
|
datetime = self.safe_string(transaction, 'applyTime')
|
|
timestamp = self.parse8601(datetime)
|
|
network = self.safe_string(transaction, 'network')
|
|
currencyId = self.safe_string(transaction, 'coin')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
if (code is not None) and (code != network) and code.find(network) >= 0:
|
|
if network is not None:
|
|
code = code.replace(network, '')
|
|
rawType = self.safe_string(transaction, 'transferType')
|
|
type = 'deposit' if (rawType == '0') else 'withdrawal'
|
|
return {
|
|
'info': transaction,
|
|
'id': id,
|
|
'txid': self.safe_string(transaction, 'txId'),
|
|
'type': type,
|
|
'currency': code,
|
|
'network': self.network_id_to_code(network),
|
|
'amount': self.safe_number(transaction, 'amount'),
|
|
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
|
|
'timestamp': timestamp,
|
|
'datetime': datetime,
|
|
'address': address,
|
|
'addressFrom': None,
|
|
'addressTo': address,
|
|
'tag': tag,
|
|
'tagFrom': tag,
|
|
'tagTo': None,
|
|
'updated': None,
|
|
'comment': self.safe_string(transaction, 'info'),
|
|
'fee': {
|
|
'currency': code,
|
|
'cost': self.safe_number(transaction, 'transactionFee'),
|
|
'rate': None,
|
|
},
|
|
'internal': None,
|
|
}
|
|
|
|
def parse_transaction_status(self, status: str):
|
|
statuses: dict = {
|
|
'0': 'pending',
|
|
'1': 'ok',
|
|
'10': 'pending',
|
|
'20': 'rejected',
|
|
'30': 'ok',
|
|
'40': 'rejected',
|
|
'50': 'ok',
|
|
'60': 'pending',
|
|
'70': 'rejected',
|
|
'2': 'pending',
|
|
'3': 'rejected',
|
|
'4': 'pending',
|
|
'5': 'rejected',
|
|
'6': 'ok',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
|
|
"""
|
|
set margin mode to 'cross' or 'isolated'
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Change%20Margin%20Type
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Set%20Margin%20Type
|
|
|
|
:param str marginMode: 'cross' or 'isolated'
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: response from the exchange
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
if market['type'] != 'swap':
|
|
raise BadSymbol(self.id + ' setMarginMode() supports swap contracts only')
|
|
marginMode = marginMode.upper()
|
|
if marginMode == 'CROSS':
|
|
marginMode = 'CROSSED'
|
|
if marginMode != 'ISOLATED' and marginMode != 'CROSSED':
|
|
raise BadRequest(self.id + ' setMarginMode() marginMode argument should be isolated or cross')
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'marginType': marginMode,
|
|
}
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('setMarginMode', market, params)
|
|
if subType == 'inverse':
|
|
return self.cswapV1PrivatePostTradeMarginType(self.extend(request, params))
|
|
else:
|
|
return self.swapV2PrivatePostTradeMarginType(self.extend(request, params))
|
|
|
|
def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
|
request: dict = {
|
|
'type': 1,
|
|
}
|
|
return self.set_margin(symbol, amount, self.extend(request, params))
|
|
|
|
def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
|
request: dict = {
|
|
'type': 2,
|
|
}
|
|
return self.set_margin(symbol, amount, self.extend(request, params))
|
|
|
|
def set_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
|
"""
|
|
Either adds or reduces margin in an isolated position in order to set the margin to a specific value
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/trade-api.html#Adjust%20isolated%20margin
|
|
|
|
:param str symbol: unified market symbol of the market to set margin in
|
|
:param float amount: the amount to set the margin to
|
|
:param dict [params]: parameters specific to the bingx api endpoint
|
|
:returns dict: A `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
|
|
"""
|
|
type = self.safe_integer(params, 'type') # 1 increase margin 2 decrease margin
|
|
if type is None:
|
|
raise ArgumentsRequired(self.id + ' setMargin() requires a type parameter either 1(increase margin) or 2(decrease margin)')
|
|
if not self.in_array(type, [1, 2]):
|
|
raise ArgumentsRequired(self.id + ' setMargin() requires a type parameter either 1(increase margin) or 2(decrease margin)')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'amount': self.amount_to_precision(market['symbol'], amount),
|
|
'type': type,
|
|
}
|
|
response = self.swapV2PrivatePostTradePositionMargin(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "amount": 1,
|
|
# "type": 1
|
|
# }
|
|
#
|
|
return self.parse_margin_modification(response, market)
|
|
|
|
def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "amount": 1,
|
|
# "type": 1
|
|
# }
|
|
#
|
|
type = self.safe_string(data, 'type')
|
|
return {
|
|
'info': data,
|
|
'symbol': self.safe_string(market, 'symbol'),
|
|
'type': 'add' if (type == '1') else 'reduce',
|
|
'marginMode': 'isolated',
|
|
'amount': self.safe_number(data, 'amount'),
|
|
'total': self.safe_number(data, 'margin'),
|
|
'code': self.safe_string(market, 'settle'),
|
|
'status': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
}
|
|
|
|
def fetch_leverage(self, symbol: str, params={}) -> Leverage:
|
|
"""
|
|
fetch the set leverage for a market
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/trade-api.html#Query%20Leverage
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20Leverage
|
|
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
if market['inverse']:
|
|
response = self.cswapV1PrivateGetTradeLeverage(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720683803391,
|
|
# "data": {
|
|
# "symbol": "SOL-USD",
|
|
# "longLeverage": 5,
|
|
# "shortLeverage": 5,
|
|
# "maxLongLeverage": 50,
|
|
# "maxShortLeverage": 50,
|
|
# "availableLongVol": "4000000",
|
|
# "availableShortVol": "4000000"
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivateGetTradeLeverage(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "longLeverage": 5,
|
|
# "shortLeverage": 5,
|
|
# "maxLongLeverage": 125,
|
|
# "maxShortLeverage": 125,
|
|
# "availableLongVol": "0.0000",
|
|
# "availableShortVol": "0.0000",
|
|
# "availableLongVal": "0.0",
|
|
# "availableShortVal": "0.0",
|
|
# "maxPositionLongVal": "0.0",
|
|
# "maxPositionShortVal": "0.0"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_leverage(data, market)
|
|
|
|
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
|
|
#
|
|
# linear swap
|
|
#
|
|
# {
|
|
# "longLeverage": 5,
|
|
# "shortLeverage": 5,
|
|
# "maxLongLeverage": 125,
|
|
# "maxShortLeverage": 125,
|
|
# "availableLongVol": "0.0000",
|
|
# "availableShortVol": "0.0000",
|
|
# "availableLongVal": "0.0",
|
|
# "availableShortVal": "0.0",
|
|
# "maxPositionLongVal": "0.0",
|
|
# "maxPositionShortVal": "0.0"
|
|
# }
|
|
#
|
|
# inverse swap
|
|
#
|
|
# {
|
|
# "symbol": "SOL-USD",
|
|
# "longLeverage": 5,
|
|
# "shortLeverage": 5,
|
|
# "maxLongLeverage": 50,
|
|
# "maxShortLeverage": 50,
|
|
# "availableLongVol": "4000000",
|
|
# "availableShortVol": "4000000"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(leverage, 'symbol')
|
|
return {
|
|
'info': leverage,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'marginMode': None,
|
|
'longLeverage': self.safe_integer(leverage, 'longLeverage'),
|
|
'shortLeverage': self.safe_integer(leverage, 'shortLeverage'),
|
|
}
|
|
|
|
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
|
"""
|
|
set the level of leverage for a market
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/trade-api.html#Switch%20Leverage
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Modify%20Leverage
|
|
|
|
: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.side]: hedged: ['long' or 'short']. one way: ['both']
|
|
:returns dict: response from the exchange
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
|
|
side = self.safe_string_upper(params, 'side')
|
|
self.check_required_argument('setLeverage', side, 'side', ['LONG', 'SHORT', 'BOTH'])
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'side': side,
|
|
'leverage': leverage,
|
|
}
|
|
if market['inverse']:
|
|
return self.cswapV1PrivatePostTradeLeverage(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720725058059,
|
|
# "data": {
|
|
# "symbol": "SOL-USD",
|
|
# "longLeverage": 10,
|
|
# "shortLeverage": 5,
|
|
# "maxLongLeverage": 50,
|
|
# "maxShortLeverage": 50,
|
|
# "availableLongVol": "4000000",
|
|
# "availableShortVol": "4000000"
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
return self.swapV2PrivatePostTradeLeverage(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "leverage": 10,
|
|
# "symbol": "BTC-USDT",
|
|
# "availableLongVol": "0.0000",
|
|
# "availableShortVol": "0.0000",
|
|
# "availableLongVal": "0.0",
|
|
# "availableShortVal": "0.0",
|
|
# "maxPositionLongVal": "0.0",
|
|
# "maxPositionShortVal": "0.0"
|
|
# }
|
|
# }
|
|
#
|
|
|
|
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Query%20transaction%20details
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20historical%20transaction%20orders
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20historical%20transaction%20details
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20Order%20Trade%20Detail
|
|
|
|
: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]: timestamp in ms for the ending date filter, default is None
|
|
:param str params['trandingUnit']: COIN(directly represent assets such and ETH) or CONT(represents the number of contract sheets)
|
|
:param str params['orderId']: the order id required for inverse swap
|
|
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {}
|
|
fills = None
|
|
response = None
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('fetchMyTrades', market, params)
|
|
if subType == 'inverse':
|
|
orderId = self.safe_string(params, 'orderId')
|
|
if orderId is None:
|
|
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires an orderId argument for inverse swap trades')
|
|
response = self.cswapV1PrivateGetTradeAllFillOrders(self.extend(request, params))
|
|
fills = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1722147756019,
|
|
# "data": [
|
|
# {
|
|
# "orderId": "1817441228670648320",
|
|
# "symbol": "SOL-USD",
|
|
# "type": "MARKET",
|
|
# "side": "BUY",
|
|
# "positionSide": "LONG",
|
|
# "tradeId": "97244554",
|
|
# "volume": "2",
|
|
# "tradePrice": "182.652",
|
|
# "amount": "20.00000000",
|
|
# "realizedPnl": "0.00000000",
|
|
# "commission": "-0.00005475",
|
|
# "currency": "SOL",
|
|
# "buyer": True,
|
|
# "maker": False,
|
|
# "tradeTime": 1722146730000
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
else:
|
|
request['symbol'] = market['id']
|
|
now = self.milliseconds()
|
|
if since is not None:
|
|
startTimeReq = 'startTime' if market['spot'] else 'startTs'
|
|
request[startTimeReq] = since
|
|
elif market['swap']:
|
|
request['startTs'] = now - 30 * 24 * 60 * 60 * 1000 # 30 days for swap
|
|
until = self.safe_integer(params, 'until')
|
|
params = self.omit(params, 'until')
|
|
if until is not None:
|
|
endTimeReq = 'endTime' if market['spot'] else 'endTs'
|
|
request[endTimeReq] = until
|
|
elif market['swap']:
|
|
request['endTs'] = now
|
|
if market['spot']:
|
|
if limit is not None:
|
|
request['limit'] = limit # default 500, maximum 1000
|
|
response = self.spotV1PrivateGetTradeMyTrades(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
fills = self.safe_list(data, 'fills', [])
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "fills": [
|
|
# {
|
|
# "symbol": "LTC-USDT",
|
|
# "id": 36237072,
|
|
# "orderId": 1674069326895775744,
|
|
# "price": "85.891",
|
|
# "qty": "0.0582",
|
|
# "quoteQty": "4.9988562000000005",
|
|
# "commission": -0.00005820000000000001,
|
|
# "commissionAsset": "LTC",
|
|
# "time": 1687964205000,
|
|
# "isBuyer": True,
|
|
# "isMaker": False
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
tradingUnit = self.safe_string_upper(params, 'tradingUnit', 'CONT')
|
|
params = self.omit(params, 'tradingUnit')
|
|
request['tradingUnit'] = tradingUnit
|
|
response = self.swapV2PrivateGetTradeAllFillOrders(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
fills = self.safe_list(data, 'fill_orders', [])
|
|
#
|
|
# {
|
|
# "code": "0",
|
|
# "msg": '',
|
|
# "data": {fill_orders: [
|
|
# {
|
|
# "volume": "0.1",
|
|
# "price": "106.75",
|
|
# "amount": "10.6750",
|
|
# "commission": "-0.0053",
|
|
# "currency": "USDT",
|
|
# "orderId": "1676213270274379776",
|
|
# "liquidatedPrice": "0.00",
|
|
# "liquidatedMarginRatio": "0.00",
|
|
# "filledTime": "2023-07-04T20:56:01.000+0800"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_trades(fills, market, since, limit, params)
|
|
|
|
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
|
#
|
|
# currencie structure
|
|
#
|
|
networks = self.safe_dict(fee, 'networks', {})
|
|
networkCodes = list(networks.keys())
|
|
networksLength = len(networkCodes)
|
|
result: dict = {
|
|
'info': networks,
|
|
'withdraw': {
|
|
'fee': None,
|
|
'percentage': None,
|
|
},
|
|
'deposit': {
|
|
'fee': None,
|
|
'percentage': None,
|
|
},
|
|
'networks': {},
|
|
}
|
|
if networksLength != 0:
|
|
for i in range(0, networksLength):
|
|
networkCode = networkCodes[i]
|
|
network = networks[networkCode]
|
|
result['networks'][networkCode] = {
|
|
'deposit': {'fee': None, 'percentage': None},
|
|
'withdraw': {'fee': self.safe_number(network, 'fee'), 'percentage': False},
|
|
}
|
|
if networksLength == 1:
|
|
result['withdraw']['fee'] = self.safe_number(network, 'withdrawFee')
|
|
result['withdraw']['percentage'] = False
|
|
return result
|
|
|
|
def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
|
"""
|
|
fetch deposit and withdraw fees
|
|
|
|
https://bingx-api.github.io/docs/#/common/account-api.html#All%20Coins'%20Information
|
|
|
|
:param str[]|None codes: list of unified currency codes
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = self.fetch_currencies(params)
|
|
depositWithdrawFees: dict = {}
|
|
responseCodes = list(response.keys())
|
|
for i in range(0, len(responseCodes)):
|
|
code = responseCodes[i]
|
|
if (codes is None) or (self.in_array(code, codes)):
|
|
entry = response[code]
|
|
depositWithdrawFees[code] = self.parse_deposit_withdraw_fee(entry)
|
|
return depositWithdrawFees
|
|
|
|
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/wallet-api.html#Withdraw
|
|
|
|
: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 int [params.walletType]: 1 fund(funding) account, 2 standard account, 3 perpetual account, 15 spot account
|
|
: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)
|
|
defaultWalletType = 15 # spot
|
|
walletType = None
|
|
walletType, params = self.handle_option_and_params_2(params, 'withdraw', 'type', 'walletType', defaultWalletType)
|
|
walletTypes = {
|
|
'funding': 1,
|
|
'fund': 1,
|
|
'standard': 2,
|
|
'perpetual': 3,
|
|
'spot': 15,
|
|
}
|
|
walletType = self.safe_integer(walletTypes, walletType, defaultWalletType)
|
|
request: dict = {
|
|
'coin': currency['id'],
|
|
'address': address,
|
|
'amount': self.currency_to_precision(code, amount),
|
|
'walletType': walletType,
|
|
}
|
|
network = self.safe_string_upper(params, 'network')
|
|
if network is not None:
|
|
request['network'] = self.network_code_to_id(network)
|
|
if tag is not None:
|
|
request['addressTag'] = tag
|
|
params = self.omit(params, ['walletType', 'network'])
|
|
response = self.walletsV1PrivatePostCapitalWithdrawApply(self.extend(request, params))
|
|
data = self.safe_value(response, 'data')
|
|
# {
|
|
# "code":0,
|
|
# "timestamp":1689258953651,
|
|
# "data":{
|
|
# "id":"1197073063359000577"
|
|
# }
|
|
# }
|
|
return self.parse_transaction(data)
|
|
|
|
def parse_params(self, params):
|
|
# sortedParams = self.keysort(params)
|
|
rawKeys = list(params.keys())
|
|
keys = self.sort(rawKeys)
|
|
for i in range(0, len(keys)):
|
|
key = keys[i]
|
|
value = params[key]
|
|
if isinstance(value, list):
|
|
arrStr = '['
|
|
for j in range(0, len(value)):
|
|
arrayElement = value[j]
|
|
if j > 0:
|
|
arrStr += ','
|
|
arrStr += str(arrayElement)
|
|
arrStr += ']'
|
|
params[key] = arrStr
|
|
return params
|
|
|
|
def fetch_my_liquidations(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
retrieves the users liquidated positions
|
|
|
|
https://bingx-api.github.io/docs/#/swapV2/trade-api.html#User's%20Force%20Orders
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20force%20orders
|
|
|
|
: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 bingx 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>`
|
|
"""
|
|
self.load_markets()
|
|
request: dict = {
|
|
'autoCloseType': 'LIQUIDATION',
|
|
}
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('fetchMyLiquidations', market, params)
|
|
response = None
|
|
liquidations = None
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PrivateGetTradeForceOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1721280071678,
|
|
# "data": [
|
|
# {
|
|
# "orderId": "string",
|
|
# "symbol": "string",
|
|
# "type": "string",
|
|
# "side": "string",
|
|
# "positionSide": "string",
|
|
# "price": "string",
|
|
# "quantity": "float64",
|
|
# "stopPrice": "string",
|
|
# "workingType": "string",
|
|
# "status": "string",
|
|
# "time": "int64",
|
|
# "avgPrice": "string",
|
|
# "executedQty": "string",
|
|
# "profit": "string",
|
|
# "commission": "string",
|
|
# "updateTime": "string"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
liquidations = self.safe_list(response, 'data', [])
|
|
else:
|
|
response = self.swapV2PrivateGetTradeForceOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "orders": [
|
|
# {
|
|
# "time": "int64",
|
|
# "symbol": "string",
|
|
# "side": "string",
|
|
# "type": "string",
|
|
# "positionSide": "string",
|
|
# "cumQuote": "string",
|
|
# "status": "string",
|
|
# "stopPrice": "string",
|
|
# "price": "string",
|
|
# "origQty": "string",
|
|
# "avgPrice": "string",
|
|
# "executedQty": "string",
|
|
# "orderId": "int64",
|
|
# "profit": "string",
|
|
# "commission": "string",
|
|
# "workingType": "string",
|
|
# "updateTime": "int64"
|
|
# },
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
liquidations = self.safe_list(data, 'orders', [])
|
|
return self.parse_liquidations(liquidations, market, since, limit)
|
|
|
|
def parse_liquidation(self, liquidation, market: Market = None):
|
|
#
|
|
# {
|
|
# "time": "int64",
|
|
# "symbol": "string",
|
|
# "side": "string",
|
|
# "type": "string",
|
|
# "positionSide": "string",
|
|
# "cumQuote": "string",
|
|
# "status": "string",
|
|
# "stopPrice": "string",
|
|
# "price": "string",
|
|
# "origQty": "string",
|
|
# "avgPrice": "string",
|
|
# "executedQty": "string",
|
|
# "orderId": "int64",
|
|
# "profit": "string",
|
|
# "commission": "string",
|
|
# "workingType": "string",
|
|
# "updateTime": "int64"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(liquidation, 'symbol')
|
|
timestamp = self.safe_integer(liquidation, 'time')
|
|
contractsString = self.safe_string(liquidation, 'executedQty')
|
|
contractSizeString = self.safe_string(market, 'contractSize')
|
|
priceString = self.safe_string(liquidation, 'avgPrice')
|
|
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 close_position(self, symbol: str, side: OrderSide = None, params={}) -> Order:
|
|
"""
|
|
closes open positions for a market
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#One-Click%20Close%20All%20Positions
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Close%20all%20positions%20in%20bulk
|
|
|
|
:param str symbol: Unified CCXT market symbol
|
|
:param str [side]: not used by bingx
|
|
:param dict [params]: extra parameters specific to the bingx api endpoint
|
|
:param str|None [params.positionId]: the id of the position you would like to close
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
positionId = self.safe_string(params, 'positionId')
|
|
request: dict = {}
|
|
response = None
|
|
if positionId is not None:
|
|
response = self.swapV1PrivatePostTradeClosePosition(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1710992264190,
|
|
# "data": {
|
|
# "orderId": 1770656007907930112,
|
|
# "positionId": "1751667128353910784",
|
|
# "symbol": "LTC-USDT",
|
|
# "side": "Ask",
|
|
# "type": "MARKET",
|
|
# "positionSide": "Long",
|
|
# "origQty": "0.2"
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
request['symbol'] = market['id']
|
|
if market['inverse']:
|
|
response = self.cswapV1PrivatePostTradeCloseAllPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720771601428,
|
|
# "data": {
|
|
# "success": ["1811673520637231104"],
|
|
# "failed": null
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivatePostTradeCloseAllPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "success": [
|
|
# 1727686766700486656,
|
|
# ],
|
|
# "failed": null
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data')
|
|
return self.parse_order(data, market)
|
|
|
|
def close_all_positions(self, params={}) -> List[Position]:
|
|
"""
|
|
closes open positions for a market
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#One-Click%20Close%20All%20Positions
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Close%20all%20positions%20in%20bulk
|
|
|
|
:param dict [params]: extra parameters specific to the bingx api endpoint
|
|
:param str [params.recvWindow]: request valid time window value
|
|
:returns dict[]: `a list of position structures <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.load_markets()
|
|
defaultRecvWindow = self.safe_integer(self.options, 'recvWindow')
|
|
recvWindow = self.safe_integer(self.parse_params, 'recvWindow', defaultRecvWindow)
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('closeAllPositions', None, params)
|
|
subType = None
|
|
subType, params = self.handle_sub_type_and_params('closeAllPositions', None, params)
|
|
if marketType == 'margin':
|
|
raise BadRequest(self.id + ' closePositions() cannot be used for ' + marketType + ' markets')
|
|
request: dict = {
|
|
'recvWindow': recvWindow,
|
|
}
|
|
response = None
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PrivatePostTradeCloseAllPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1720771601428,
|
|
# "data": {
|
|
# "success": ["1811673520637231104"],
|
|
# "failed": null
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivatePostTradeCloseAllPositions(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "success": [
|
|
# 1727686766700486656,
|
|
# 1727686767048613888
|
|
# ],
|
|
# "failed": null
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
success = self.safe_list(data, 'success', [])
|
|
positions = []
|
|
for i in range(0, len(success)):
|
|
position = self.parse_position({'positionId': success[i]})
|
|
positions.append(position)
|
|
return positions
|
|
|
|
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://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Get%20Position%20Mode
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
: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.swapV1PrivateGetPositionSideDual(params)
|
|
#
|
|
# {
|
|
# "code": "0",
|
|
# "msg": "",
|
|
# "timeStamp": "1709002057516",
|
|
# "data": {
|
|
# "dualSidePosition": "false"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
dualSidePosition = self.safe_string(data, 'dualSidePosition')
|
|
return {
|
|
'info': response,
|
|
'hedged': (dualSidePosition == 'true'),
|
|
}
|
|
|
|
def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
|
|
"""
|
|
set hedged to True or False for a market
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Set%20Position%20Mode
|
|
|
|
: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
|
|
"""
|
|
dualSidePosition = None
|
|
if hedged:
|
|
dualSidePosition = 'true'
|
|
else:
|
|
dualSidePosition = 'false'
|
|
request: dict = {
|
|
'dualSidePosition': dualSidePosition,
|
|
}
|
|
#
|
|
# {
|
|
# code: '0',
|
|
# msg: '',
|
|
# timeStamp: '1703327432734',
|
|
# data: {dualSidePosition: 'false'}
|
|
# }
|
|
#
|
|
return self.swapV1PrivatePostPositionSideDual(self.extend(request, params))
|
|
|
|
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order:
|
|
"""
|
|
cancels an order and places a new order
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Cancel%20order%20and%20place%20a%20new%20order # spot
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Cancel%20an%20order%20and%20then%20Place%20a%20new%20order # swap
|
|
|
|
:param str id: order id
|
|
: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 the currency you want to trade in units of the 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.triggerPrice]: Trigger price used for TAKE_STOP_LIMIT, TAKE_STOP_MARKET, TRIGGER_LIMIT, TRIGGER_MARKET order types.
|
|
:param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered
|
|
:param float [params.takeProfit.triggerPrice]: take profit trigger price
|
|
:param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered
|
|
:param float [params.stopLoss.triggerPrice]: stop loss trigger price
|
|
|
|
EXCHANGE SPECIFIC PARAMETERS
|
|
:param str [params.cancelClientOrderID]: the user-defined id of the order to be canceled, 1-40 characters, different orders cannot use the same clientOrderID, only supports a query range of 2 hours
|
|
:param str [params.cancelRestrictions]: cancel orders with specified status, NEW: New order, PENDING: Pending order, PARTIALLY_FILLED: Partially filled
|
|
:param str [params.cancelReplaceMode]: STOP_ON_FAILURE - if the cancel order fails, it will not continue to place a new order, ALLOW_FAILURE - regardless of whether the cancel order succeeds or fails, it will continue to place a new order
|
|
:param float [params.quoteOrderQty]: order amount
|
|
:param str [params.newClientOrderId]: custom order id consisting of letters, numbers, and _, 1-40 characters, different orders cannot use the same newClientOrderId.
|
|
:param str [params.positionSide]: *contract only* position direction, required for single position, for both long and short positions only LONG or SHORT can be chosen, defaults to LONG if empty
|
|
:param str [params.reduceOnly]: *contract only* True or False, default=false for single position mode. self parameter is not accepted for both long and short positions mode
|
|
:param float [params.priceRate]: *contract only* for type TRAILING_STOP_Market or TRAILING_TP_SL, Max = 1
|
|
:param str [params.workingType]: *contract only* StopPrice trigger price types, MARK_PRICE(default), CONTRACT_PRICE, or INDEX_PRICE
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request = self.create_order_request(symbol, type, side, amount, price, params)
|
|
request['cancelOrderId'] = id
|
|
request['cancelReplaceMode'] = 'STOP_ON_FAILURE'
|
|
response = None
|
|
if market['swap']:
|
|
response = self.swapV1PrivatePostTradeCancelReplace(self.extend(request, params))
|
|
#
|
|
# {
|
|
# code: '0',
|
|
# msg: '',
|
|
# data: {
|
|
# cancelResult: 'true',
|
|
# cancelMsg: '',
|
|
# cancelResponse: {
|
|
# cancelClientOrderId: '',
|
|
# cancelOrderId: '1755336244265705472',
|
|
# symbol: 'SOL-USDT',
|
|
# orderId: '1755336244265705472',
|
|
# side: 'SELL',
|
|
# positionSide: 'SHORT',
|
|
# type: 'LIMIT',
|
|
# origQty: '1',
|
|
# price: '100.000',
|
|
# executedQty: '0',
|
|
# avgPrice: '0.000',
|
|
# cumQuote: '0',
|
|
# stopPrice: '',
|
|
# profit: '0.0000',
|
|
# commission: '0.000000',
|
|
# status: 'PENDING',
|
|
# time: '1707339747860',
|
|
# updateTime: '1707339747860',
|
|
# clientOrderId: '',
|
|
# leverage: '20X',
|
|
# workingType: 'MARK_PRICE',
|
|
# onlyOnePosition: False,
|
|
# reduceOnly: False
|
|
# },
|
|
# replaceResult: 'true',
|
|
# replaceMsg: '',
|
|
# newOrderResponse: {
|
|
# orderId: '1755338440612995072',
|
|
# symbol: 'SOL-USDT',
|
|
# positionSide: 'SHORT',
|
|
# side: 'SELL',
|
|
# type: 'LIMIT',
|
|
# price: '99',
|
|
# quantity: '2',
|
|
# stopPrice: '0',
|
|
# workingType: 'MARK_PRICE',
|
|
# clientOrderID: '',
|
|
# timeInForce: 'GTC',
|
|
# priceRate: '0',
|
|
# stopLoss: '',
|
|
# takeProfit: '',
|
|
# reduceOnly: False
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.spotV1PrivatePostTradeOrderCancelReplace(self.extend(request, params))
|
|
#
|
|
# {
|
|
# code: '0',
|
|
# msg: '',
|
|
# debugMsg: '',
|
|
# data: {
|
|
# cancelResult: {code: '0', msg: '', result: True},
|
|
# openResult: {code: '0', msg: '', result: True},
|
|
# orderOpenResponse: {
|
|
# symbol: 'SOL-USDT',
|
|
# orderId: '1755334007697866752',
|
|
# transactTime: '1707339214620',
|
|
# price: '99',
|
|
# stopPrice: '0',
|
|
# origQty: '0.2',
|
|
# executedQty: '0',
|
|
# cummulativeQuoteQty: '0',
|
|
# status: 'PENDING',
|
|
# type: 'LIMIT',
|
|
# side: 'SELL',
|
|
# clientOrderID: ''
|
|
# },
|
|
# orderCancelResponse: {
|
|
# symbol: 'SOL-USDT',
|
|
# orderId: '1755117055251480576',
|
|
# price: '100',
|
|
# stopPrice: '0',
|
|
# origQty: '0.2',
|
|
# executedQty: '0',
|
|
# cummulativeQuoteQty: '0',
|
|
# status: 'CANCELED',
|
|
# type: 'LIMIT',
|
|
# side: 'SELL'
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data')
|
|
return self.parse_order(data, market)
|
|
|
|
def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode:
|
|
"""
|
|
fetches the margin mode of the trading pair
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/trade-api.html#Query%20Margin%20Type
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20Margin%20Type
|
|
|
|
:param str symbol: unified symbol of the market to fetch the margin mode for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `margin mode structure <https://docs.ccxt.com/#/?id=margin-mode-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
subType = None
|
|
response = None
|
|
subType, params = self.handle_sub_type_and_params('fetchMarginMode', market, params)
|
|
if subType == 'inverse':
|
|
response = self.cswapV1PrivateGetTradeMarginType(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1721966069132,
|
|
# "data": {
|
|
# "symbol": "SOL-USD",
|
|
# "marginType": "CROSSED"
|
|
# }
|
|
# }
|
|
#
|
|
else:
|
|
response = self.swapV2PrivateGetTradeMarginType(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "marginType": "CROSSED"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_margin_mode(data, market)
|
|
|
|
def parse_margin_mode(self, marginMode: dict, market=None) -> MarginMode:
|
|
marketId = self.safe_string(marginMode, 'symbol')
|
|
marginType = self.safe_string_lower(marginMode, 'marginType')
|
|
marginType = 'cross' if (marginType == 'crossed') else marginType
|
|
return {
|
|
'info': marginMode,
|
|
'symbol': self.safe_symbol(marketId, market, '-', 'swap'),
|
|
'marginMode': marginType,
|
|
}
|
|
|
|
def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
|
"""
|
|
fetch the trading fees for a market
|
|
|
|
https://bingx-api.github.io/docs/#/en-us/spot/trade-api.html#Query%20Trading%20Commission%20Rate
|
|
https://bingx-api.github.io/docs/#/en-us/swapV2/account-api.html#Query%20Trading%20Commission%20Rate
|
|
https://bingx-api.github.io/docs/#/en-us/cswap/trade-api.html#Query%20Trade%20Commission%20Rate
|
|
|
|
: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)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
commission: dict = {}
|
|
data = self.safe_dict(response, 'data', {})
|
|
if market['spot']:
|
|
response = self.spotV1PrivateGetUserCommissionRate(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "debugMsg": "",
|
|
# "data": {
|
|
# "takerCommissionRate": 0.001,
|
|
# "makerCommissionRate": 0.001
|
|
# }
|
|
# }
|
|
#
|
|
commission = data
|
|
else:
|
|
if market['inverse']:
|
|
response = self.cswapV1PrivateGetUserCommissionRate(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "timestamp": 1721365261438,
|
|
# "data": {
|
|
# "takerCommissionRate": "0.0005",
|
|
# "makerCommissionRate": "0.0002"
|
|
# }
|
|
# }
|
|
#
|
|
commission = data
|
|
else:
|
|
response = self.swapV2PrivateGetUserCommissionRate(params)
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "",
|
|
# "data": {
|
|
# "commission": {
|
|
# "takerCommissionRate": 0.0005,
|
|
# "makerCommissionRate": 0.0002
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
commission = self.safe_dict(data, 'commission', {})
|
|
return self.parse_trading_fee(commission, market)
|
|
|
|
def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
|
|
#
|
|
# {
|
|
# "takerCommissionRate": 0.001,
|
|
# "makerCommissionRate": 0.001
|
|
# }
|
|
#
|
|
symbol = market['symbol'] if (market is not None) else None
|
|
return {
|
|
'info': fee,
|
|
'symbol': symbol,
|
|
'maker': self.safe_number(fee, 'makerCommissionRate'),
|
|
'taker': self.safe_number(fee, 'takerCommissionRate'),
|
|
'percentage': False,
|
|
'tierBased': False,
|
|
}
|
|
|
|
def custom_encode(self, params):
|
|
# sortedParams = self.keysort(params)
|
|
rawKeys = list(params.keys())
|
|
keys = self.sort(rawKeys)
|
|
adjustedValue = None
|
|
result = None
|
|
for i in range(0, len(keys)):
|
|
key = keys[i]
|
|
value = params[key]
|
|
if isinstance(value, list):
|
|
arrStr = None
|
|
for j in range(0, len(value)):
|
|
arrayElement = value[j]
|
|
isString = (isinstance(arrayElement, str))
|
|
if isString:
|
|
if j > 0:
|
|
arrStr += ',' + '"' + str(arrayElement) + '"'
|
|
else:
|
|
arrStr = '"' + str(arrayElement) + '"'
|
|
else:
|
|
if j > 0:
|
|
arrStr += ',' + str(arrayElement)
|
|
else:
|
|
arrStr = str(arrayElement)
|
|
adjustedValue = '[' + arrStr + ']'
|
|
value = adjustedValue
|
|
if i == 0:
|
|
result = key + '=' + value
|
|
else:
|
|
result += '&' + key + '=' + value
|
|
return result
|
|
|
|
def sign(self, path, section='public', method='GET', params={}, headers=None, body=None):
|
|
type = section[0]
|
|
version = section[1]
|
|
access = section[2]
|
|
isSandbox = self.safe_bool(self.options, 'sandboxMode', False)
|
|
if isSandbox and (type != 'swap'):
|
|
raise NotSupported(self.id + ' does not have a testnet/sandbox URL for ' + type + ' endpoints')
|
|
url = self.implode_hostname(self.urls['api'][type])
|
|
path = self.implode_params(path, params)
|
|
versionIsTransfer = (version == 'transfer')
|
|
versionIsAsset = (version == 'asset')
|
|
if versionIsTransfer or versionIsAsset:
|
|
if versionIsTransfer:
|
|
type = 'account/transfer'
|
|
else:
|
|
type = 'api/asset'
|
|
version = section[2]
|
|
access = section[3]
|
|
if path != 'account/apiPermissions':
|
|
if type == 'spot' and version == 'v3':
|
|
url += '/api'
|
|
else:
|
|
url += '/' + type
|
|
url += '/' + version + '/' + path
|
|
params = self.omit(params, self.extract_params(path))
|
|
params['timestamp'] = self.nonce()
|
|
params = self.keysort(params)
|
|
if access == 'public':
|
|
if params:
|
|
url += '?' + self.urlencode(params)
|
|
elif access == 'private':
|
|
self.check_required_credentials()
|
|
isJsonContentType = (((type == 'subAccount') or (type == 'account/transfer')) and (method == 'POST'))
|
|
parsedParams = None
|
|
encodeRequest = None
|
|
if isJsonContentType:
|
|
encodeRequest = self.custom_encode(params)
|
|
else:
|
|
parsedParams = self.parse_params(params)
|
|
encodeRequest = self.rawencode(parsedParams, True)
|
|
signature = self.hmac(self.encode(encodeRequest), self.encode(self.secret), hashlib.sha256)
|
|
headers = {
|
|
'X-BX-APIKEY': self.apiKey,
|
|
'X-SOURCE-KEY': self.safe_string(self.options, 'broker', 'CCXT'),
|
|
}
|
|
if isJsonContentType:
|
|
headers['Content-Type'] = 'application/json'
|
|
params['signature'] = signature
|
|
body = self.json(params)
|
|
else:
|
|
query = self.urlencode(parsedParams, True)
|
|
url += '?' + query + '&' + 'signature=' + signature
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def nonce(self):
|
|
return self.milliseconds()
|
|
|
|
def set_sandbox_mode(self, enable: bool):
|
|
super(bingx, self).set_sandbox_mode(enable)
|
|
self.options['sandboxMode'] = enable
|
|
|
|
def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
if response is None:
|
|
return None # fallback to default error handler
|
|
#
|
|
# {
|
|
# "code": 80014,
|
|
# "msg": "Invalid parameters, err:Key: 'GetTickerRequest.Symbol' Error:Field validation for "Symbol" failed on the "len=0|endswith=-USDT" tag",
|
|
# "data": {
|
|
# }
|
|
# }
|
|
#
|
|
code = self.safe_string(response, 'code')
|
|
message = self.safe_string(response, 'msg')
|
|
transferErrorMsg = self.safe_string(response, 'transferErrorMsg') # handling with errors from transfer endpoint
|
|
if (transferErrorMsg is not None) or (code is not None and code != '0'):
|
|
if transferErrorMsg is not None:
|
|
message = transferErrorMsg
|
|
feedback = self.id + ' ' + body
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
raise ExchangeError(feedback) # unknown message
|
|
return None
|