13487 lines
673 KiB
Python
13487 lines
673 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.binance import ImplicitAPI
|
||
import hashlib
|
||
import json
|
||
from ccxt.base.types import Any, Balances, BorrowInterest, Conversion, CrossBorrowRate, Currencies, Currency, DepositAddress, Greeks, Int, IsolatedBorrowRate, IsolatedBorrowRates, LedgerEntry, Leverage, Leverages, LeverageTier, LeverageTiers, LongShortRatio, MarginMode, MarginModes, MarginModification, Market, Num, Option, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, TradingFees, Transaction, MarketInterface, TransferEntry
|
||
from typing import List
|
||
from ccxt.base.errors import ExchangeError
|
||
from ccxt.base.errors import AuthenticationError
|
||
from ccxt.base.errors import PermissionDenied
|
||
from ccxt.base.errors import AccountSuspended
|
||
from ccxt.base.errors import ArgumentsRequired
|
||
from ccxt.base.errors import BadRequest
|
||
from ccxt.base.errors import BadSymbol
|
||
from ccxt.base.errors import OperationRejected
|
||
from ccxt.base.errors import MarginModeAlreadySet
|
||
from ccxt.base.errors import MarketClosed
|
||
from ccxt.base.errors import InsufficientFunds
|
||
from ccxt.base.errors import InvalidOrder
|
||
from ccxt.base.errors import OrderNotFound
|
||
from ccxt.base.errors import OrderImmediatelyFillable
|
||
from ccxt.base.errors import OrderNotFillable
|
||
from ccxt.base.errors import NotSupported
|
||
from ccxt.base.errors import OperationFailed
|
||
from ccxt.base.errors import DDoSProtection
|
||
from ccxt.base.errors import RateLimitExceeded
|
||
from ccxt.base.errors import OnMaintenance
|
||
from ccxt.base.errors import InvalidNonce
|
||
from ccxt.base.errors import RequestTimeout
|
||
from ccxt.base.errors import BadResponse
|
||
from ccxt.base.decimal_to_precision import TRUNCATE
|
||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
||
from ccxt.base.precise import Precise
|
||
|
||
|
||
class binance(Exchange, ImplicitAPI):
|
||
|
||
def describe(self) -> Any:
|
||
return self.deep_extend(super(binance, self).describe(), {
|
||
'id': 'binance',
|
||
'name': 'Binance',
|
||
'countries': [], # Japan
|
||
'rateLimit': 50,
|
||
'certified': True,
|
||
'pro': True,
|
||
# new metainfo2 interface
|
||
'has': {
|
||
'CORS': None,
|
||
'spot': True,
|
||
'margin': True,
|
||
'swap': True,
|
||
'future': True,
|
||
'option': True,
|
||
'addMargin': True,
|
||
'borrowCrossMargin': True,
|
||
'borrowIsolatedMargin': True,
|
||
'cancelAllOrders': True,
|
||
'cancelOrder': True,
|
||
'cancelOrders': True, # contract only
|
||
'closeAllPositions': False,
|
||
'closePosition': False, # exchange specific closePosition parameter for binance createOrder is not synonymous with how CCXT uses closePositions
|
||
'createConvertTrade': True,
|
||
'createDepositAddress': False,
|
||
'createLimitBuyOrder': True,
|
||
'createLimitSellOrder': True,
|
||
'createMarketBuyOrder': True,
|
||
'createMarketBuyOrderWithCost': True,
|
||
'createMarketOrderWithCost': True,
|
||
'createMarketSellOrder': True,
|
||
'createMarketSellOrderWithCost': True,
|
||
'createOrder': True,
|
||
'createOrders': True,
|
||
'createOrderWithTakeProfitAndStopLoss': False,
|
||
'createPostOnlyOrder': True,
|
||
'createReduceOnlyOrder': True,
|
||
'createStopLimitOrder': True,
|
||
'createStopLossOrder': True,
|
||
'createStopMarketOrder': False,
|
||
'createStopOrder': True,
|
||
'createTakeProfitOrder': True,
|
||
'createTrailingPercentOrder': True,
|
||
'createTriggerOrder': True,
|
||
'editOrder': True,
|
||
'editOrders': True,
|
||
'fetchAccounts': None,
|
||
'fetchAllGreeks': True,
|
||
'fetchBalance': True,
|
||
'fetchBidsAsks': True,
|
||
'fetchBorrowInterest': True,
|
||
'fetchBorrowRateHistories': False,
|
||
'fetchBorrowRateHistory': True,
|
||
'fetchCanceledAndClosedOrders': 'emulated',
|
||
'fetchCanceledOrders': 'emulated',
|
||
'fetchClosedOrder': False,
|
||
'fetchClosedOrders': 'emulated',
|
||
'fetchConvertCurrencies': True,
|
||
'fetchConvertQuote': True,
|
||
'fetchConvertTrade': True,
|
||
'fetchConvertTradeHistory': True,
|
||
'fetchCrossBorrowRate': True,
|
||
'fetchCrossBorrowRates': False,
|
||
'fetchCurrencies': True,
|
||
'fetchDeposit': False,
|
||
'fetchDepositAddress': True,
|
||
'fetchDepositAddresses': False,
|
||
'fetchDepositAddressesByNetwork': False,
|
||
'fetchDeposits': True,
|
||
'fetchDepositsWithdrawals': False,
|
||
'fetchDepositWithdrawFee': 'emulated',
|
||
'fetchDepositWithdrawFees': True,
|
||
'fetchFundingHistory': True,
|
||
'fetchFundingInterval': 'emulated',
|
||
'fetchFundingIntervals': True,
|
||
'fetchFundingRate': True,
|
||
'fetchFundingRateHistory': True,
|
||
'fetchFundingRates': True,
|
||
'fetchGreeks': True,
|
||
'fetchIndexOHLCV': True,
|
||
'fetchIsolatedBorrowRate': 'emulated',
|
||
'fetchIsolatedBorrowRates': True,
|
||
'fetchL3OrderBook': False,
|
||
'fetchLastPrices': True,
|
||
'fetchLedger': True,
|
||
'fetchLedgerEntry': True,
|
||
'fetchLeverage': 'emulated',
|
||
'fetchLeverages': True,
|
||
'fetchLeverageTiers': True,
|
||
'fetchLiquidations': False,
|
||
'fetchLongShortRatio': False,
|
||
'fetchLongShortRatioHistory': True,
|
||
'fetchMarginAdjustmentHistory': True,
|
||
'fetchMarginMode': True,
|
||
'fetchMarginModes': True,
|
||
'fetchMarketLeverageTiers': 'emulated',
|
||
'fetchMarkets': True,
|
||
'fetchMarkOHLCV': True,
|
||
'fetchMarkPrice': True,
|
||
'fetchMarkPrices': True,
|
||
'fetchMyLiquidations': True,
|
||
'fetchMySettlementHistory': True,
|
||
'fetchMyTrades': True,
|
||
'fetchOHLCV': True,
|
||
'fetchOpenInterest': True,
|
||
'fetchOpenInterestHistory': True,
|
||
'fetchOpenOrder': True,
|
||
'fetchOpenOrders': True,
|
||
'fetchOption': True,
|
||
'fetchOptionChain': False,
|
||
'fetchOrder': True,
|
||
'fetchOrderBook': True,
|
||
'fetchOrderBooks': False,
|
||
'fetchOrders': True,
|
||
'fetchOrderTrades': True,
|
||
'fetchPosition': True,
|
||
'fetchPositionHistory': False,
|
||
'fetchPositionMode': True,
|
||
'fetchPositions': True,
|
||
'fetchPositionsHistory': False,
|
||
'fetchPositionsRisk': True,
|
||
'fetchPremiumIndexOHLCV': True,
|
||
'fetchSettlementHistory': True,
|
||
'fetchStatus': True,
|
||
'fetchTicker': True,
|
||
'fetchTickers': True,
|
||
'fetchTime': True,
|
||
'fetchTrades': True,
|
||
'fetchTradingFee': True,
|
||
'fetchTradingFees': True,
|
||
'fetchTradingLimits': 'emulated',
|
||
'fetchTransactionFee': 'emulated',
|
||
'fetchTransactionFees': True,
|
||
'fetchTransactions': False,
|
||
'fetchTransfer': False,
|
||
'fetchTransfers': True,
|
||
'fetchUnderlyingAssets': False,
|
||
'fetchVolatilityHistory': False,
|
||
'fetchWithdrawAddresses': False,
|
||
'fetchWithdrawal': False,
|
||
'fetchWithdrawals': True,
|
||
'fetchWithdrawalWhitelist': False,
|
||
'reduceMargin': True,
|
||
'repayCrossMargin': True,
|
||
'repayIsolatedMargin': True,
|
||
'sandbox': True,
|
||
'setLeverage': True,
|
||
'setMargin': False,
|
||
'setMarginMode': True,
|
||
'setPositionMode': True,
|
||
'signIn': False,
|
||
'transfer': True,
|
||
'withdraw': True,
|
||
},
|
||
'timeframes': {
|
||
'1s': '1s', # spot only for now
|
||
'1m': '1m',
|
||
'3m': '3m',
|
||
'5m': '5m',
|
||
'15m': '15m',
|
||
'30m': '30m',
|
||
'1h': '1h',
|
||
'2h': '2h',
|
||
'4h': '4h',
|
||
'6h': '6h',
|
||
'8h': '8h',
|
||
'12h': '12h',
|
||
'1d': '1d',
|
||
'3d': '3d',
|
||
'1w': '1w',
|
||
'1M': '1M',
|
||
},
|
||
'urls': {
|
||
'logo': 'https://github.com/user-attachments/assets/e9419b93-ccb0-46aa-9bff-c883f096274b',
|
||
'test': {
|
||
'dapiPublic': 'https://testnet.binancefuture.com/dapi/v1',
|
||
'dapiPrivate': 'https://testnet.binancefuture.com/dapi/v1',
|
||
'dapiPrivateV2': 'https://testnet.binancefuture.com/dapi/v2',
|
||
'fapiPublic': 'https://testnet.binancefuture.com/fapi/v1',
|
||
'fapiPublicV2': 'https://testnet.binancefuture.com/fapi/v2',
|
||
'fapiPublicV3': 'https://testnet.binancefuture.com/fapi/v3',
|
||
'fapiPrivate': 'https://testnet.binancefuture.com/fapi/v1',
|
||
'fapiPrivateV2': 'https://testnet.binancefuture.com/fapi/v2',
|
||
'fapiPrivateV3': 'https://testnet.binancefuture.com/fapi/v3',
|
||
'public': 'https://testnet.binance.vision/api/v3',
|
||
'private': 'https://testnet.binance.vision/api/v3',
|
||
'v1': 'https://testnet.binance.vision/api/v1',
|
||
},
|
||
'demo': {
|
||
'dapiPublic': 'https://demo-dapi.binance.com/dapi/v1',
|
||
'dapiPrivate': 'https://demo-dapi.binance.com/dapi/v1',
|
||
'dapiPrivateV2': 'https://demo-dapi.binance.com/dapi/v2',
|
||
'fapiPublic': 'https://demo-fapi.binance.com/fapi/v1',
|
||
'fapiPublicV2': 'https://demo-fapi.binance.com/fapi/v2',
|
||
'fapiPublicV3': 'https://demo-fapi.binance.com/fapi/v3',
|
||
'fapiPrivate': 'https://demo-fapi.binance.com/fapi/v1',
|
||
'fapiPrivateV2': 'https://demo-fapi.binance.com/fapi/v2',
|
||
'fapiPrivateV3': 'https://demo-fapi.binance.com/fapi/v3',
|
||
'public': 'https://demo-api.binance.com/api/v3',
|
||
'private': 'https://demo-api.binance.com/api/v3',
|
||
'v1': 'https://demo-api.binance.com/api/v1',
|
||
},
|
||
'api': {
|
||
'sapi': 'https://api.binance.com/sapi/v1',
|
||
'sapiV2': 'https://api.binance.com/sapi/v2',
|
||
'sapiV3': 'https://api.binance.com/sapi/v3',
|
||
'sapiV4': 'https://api.binance.com/sapi/v4',
|
||
'dapiPublic': 'https://dapi.binance.com/dapi/v1',
|
||
'dapiPrivate': 'https://dapi.binance.com/dapi/v1',
|
||
'eapiPublic': 'https://eapi.binance.com/eapi/v1',
|
||
'eapiPrivate': 'https://eapi.binance.com/eapi/v1',
|
||
'dapiPrivateV2': 'https://dapi.binance.com/dapi/v2',
|
||
'dapiData': 'https://dapi.binance.com/futures/data',
|
||
'fapiPublic': 'https://fapi.binance.com/fapi/v1',
|
||
'fapiPublicV2': 'https://fapi.binance.com/fapi/v2',
|
||
'fapiPublicV3': 'https://fapi.binance.com/fapi/v3',
|
||
'fapiPrivate': 'https://fapi.binance.com/fapi/v1',
|
||
'fapiPrivateV2': 'https://fapi.binance.com/fapi/v2',
|
||
'fapiPrivateV3': 'https://fapi.binance.com/fapi/v3',
|
||
'fapiData': 'https://fapi.binance.com/futures/data',
|
||
'public': 'https://api.binance.com/api/v3',
|
||
'private': 'https://api.binance.com/api/v3',
|
||
'v1': 'https://api.binance.com/api/v1',
|
||
'papi': 'https://papi.binance.com/papi/v1',
|
||
'papiV2': 'https://papi.binance.com/papi/v2',
|
||
},
|
||
'www': 'https://www.binance.com',
|
||
'referral': {
|
||
'url': 'https://accounts.binance.com/register?ref=CCXTCOM',
|
||
'discount': 0.1,
|
||
},
|
||
'doc': [
|
||
'https://developers.binance.com/en',
|
||
],
|
||
'api_management': 'https://www.binance.com/en/usercenter/settings/api-management',
|
||
'fees': 'https://www.binance.com/en/fee/schedule',
|
||
},
|
||
'api': {
|
||
# the API structure below will need 3-layer apidefs
|
||
'sapi': {
|
||
# IP(sapi) request rate limit of 12 000 per minute
|
||
# 1 IP(sapi) => cost = 0.1 =>(1000 / (50 * 0.1)) * 60 = 12000
|
||
# 10 IP(sapi) => cost = 1
|
||
# UID(sapi) request rate limit of 180 000 per minute
|
||
# 1 UID(sapi) => cost = 0.006667 =>(1000 / (50 * 0.006667)) * 60 = 180000
|
||
'get': {
|
||
# copy trading
|
||
'copyTrading/futures/userStatus': 2,
|
||
'copyTrading/futures/leadSymbol': 2,
|
||
'system/status': 0.1,
|
||
# these endpoints require self.apiKey
|
||
'accountSnapshot': 240, # Weight(IP): 2400 => cost = 0.1 * 2400 = 240
|
||
'account/info': 0.1,
|
||
'margin/asset': 1, # Weight(IP): 10 => cost = 0.1 * 10 = 1
|
||
'margin/pair': 1,
|
||
'margin/allAssets': 0.1,
|
||
'margin/allPairs': 0.1,
|
||
'margin/priceIndex': 1,
|
||
# these endpoints require self.apiKey + self.secret
|
||
'spot/delist-schedule': 10,
|
||
'asset/assetDividend': 1,
|
||
'asset/dribblet': 0.1,
|
||
'asset/transfer': 0.1,
|
||
'asset/assetDetail': 0.1,
|
||
'asset/tradeFee': 0.1,
|
||
'asset/ledger-transfer/cloud-mining/queryByPage': 4.0002, # Weight(UID): 600 => cost = 0.006667 * 600 = 4.0002
|
||
'asset/convert-transfer/queryByPage': 0.033335,
|
||
'asset/wallet/balance': 6, # Weight(IP): 60 => cost = 0.1 * 60 = 6
|
||
'asset/custody/transfer-history': 6, # Weight(IP): 60 => cost = 0.1 * 60 = 6
|
||
'margin/borrow-repay': 1,
|
||
'margin/loan': 1,
|
||
'margin/repay': 1,
|
||
'margin/account': 1,
|
||
'margin/transfer': 0.1,
|
||
'margin/interestHistory': 0.1,
|
||
'margin/forceLiquidationRec': 0.1,
|
||
'margin/order': 1,
|
||
'margin/openOrders': 1,
|
||
'margin/allOrders': 20, # Weight(IP): 200 => cost = 0.1 * 200 = 20
|
||
'margin/myTrades': 1,
|
||
'margin/maxBorrowable': 5, # Weight(IP): 50 => cost = 0.1 * 50 = 5
|
||
'margin/maxTransferable': 5,
|
||
'margin/tradeCoeff': 1,
|
||
'margin/isolated/transfer': 0.1,
|
||
'margin/isolated/account': 1,
|
||
'margin/isolated/pair': 1,
|
||
'margin/isolated/allPairs': 1,
|
||
'margin/isolated/accountLimit': 0.1,
|
||
'margin/interestRateHistory': 0.1,
|
||
'margin/orderList': 1,
|
||
'margin/allOrderList': 20, # Weight(IP): 200 => cost = 0.1 * 200 = 20
|
||
'margin/openOrderList': 1,
|
||
'margin/crossMarginData': {'cost': 0.1, 'noCoin': 0.5},
|
||
'margin/isolatedMarginData': {'cost': 0.1, 'noCoin': 1},
|
||
'margin/isolatedMarginTier': 0.1,
|
||
'margin/rateLimit/order': 2,
|
||
'margin/dribblet': 0.1,
|
||
'margin/dust': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20
|
||
'margin/crossMarginCollateralRatio': 10,
|
||
'margin/exchange-small-liability': 0.6667,
|
||
'margin/exchange-small-liability-history': 0.6667,
|
||
'margin/next-hourly-interest-rate': 0.6667,
|
||
'margin/capital-flow': 10, # Weight(IP): 100 => cost = 0.1 * 100 = 10
|
||
'margin/delist-schedule': 10, # Weight(IP): 100 => cost = 0.1 * 100 = 10
|
||
'margin/available-inventory': 0.3334, # Weight(UID): 50 => cost = 0.006667 * 50 = 0.3334
|
||
'margin/leverageBracket': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'loan/vip/loanable/data': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/vip/collateral/data': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/vip/request/data': 2.6668, # Weight(UID): 400 => cost = 0.006667 * 400 = 2.6668
|
||
'loan/vip/request/interestRate': 2.6668, # Weight(UID): 400 => cost = 0.006667 * 400 = 2.6668
|
||
'loan/income': 40.002, # Weight(UID): 6000 => cost = 0.006667 * 6000 = 40.002
|
||
'loan/ongoing/orders': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/ltv/adjustment/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/borrow/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/repay/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/loanable/data': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/collateral/data': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/repay/collateral/rate': 600, # Weight(IP): 6000 => cost = 0.1 * 6000 = 600
|
||
'loan/flexible/ongoing/orders': 30, # TODO: Deprecating at 2024-04-24 03:00(UTC)
|
||
'loan/flexible/borrow/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40, check flexible rate loans order history before 2024-02-27 08:00(UTC)
|
||
'loan/flexible/repay/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40, check flexible rate loans order history before 2024-02-27 08:00(UTC)
|
||
'loan/flexible/ltv/adjustment/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40, check flexible rate loans order history before 2024-02-27 08:00(UTC)
|
||
'loan/vip/ongoing/orders': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/vip/repay/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/vip/collateral/account': 600, # Weight(IP): 6000 => cost = 0.1 * 6000 = 600
|
||
'fiat/orders': 600.03, # Weight(UID): 90000 => cost = 0.006667 * 90000 = 600.03
|
||
'fiat/payments': 0.1,
|
||
'futures/transfer': 1,
|
||
'futures/histDataLink': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'rebate/taxQuery': 80.004, # Weight(UID): 12000 => cost = 0.006667 * 12000 = 80.004
|
||
'capital/config/getall': 1, # get networks for withdrawing USDT ERC20 vs USDT Omni
|
||
'capital/deposit/address': 1,
|
||
'capital/deposit/address/list': 1,
|
||
'capital/deposit/hisrec': 0.1,
|
||
'capital/deposit/subAddress': 0.1,
|
||
'capital/deposit/subHisrec': 0.1,
|
||
'capital/withdraw/history': 2, # Weight(UID): 18000 + (Additional: 10 requests per second => cost = ( 1000 / rateLimit ) / 10 = 2
|
||
'capital/withdraw/address/list': 10,
|
||
'capital/contract/convertible-coins': 4.0002, # Weight(UID): 600 => cost = 0.006667 * 600 = 4.0002
|
||
'convert/tradeFlow': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'convert/exchangeInfo': 50,
|
||
'convert/assetInfo': 10,
|
||
'convert/orderStatus': 0.6667,
|
||
'convert/limit/queryOpenOrders': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'account/status': 0.1,
|
||
'account/apiTradingStatus': 0.1,
|
||
'account/apiRestrictions/ipRestriction': 0.1,
|
||
'bnbBurn': 0.1,
|
||
'sub-account/futures/account': 1,
|
||
'sub-account/futures/accountSummary': 0.1,
|
||
'sub-account/futures/positionRisk': 1,
|
||
'sub-account/futures/internalTransfer': 0.1,
|
||
'sub-account/list': 0.1,
|
||
'sub-account/margin/account': 1,
|
||
'sub-account/margin/accountSummary': 1,
|
||
'sub-account/spotSummary': 0.1,
|
||
'sub-account/status': 1,
|
||
'sub-account/sub/transfer/history': 0.1,
|
||
'sub-account/transfer/subUserHistory': 0.1,
|
||
'sub-account/universalTransfer': 0.1,
|
||
'sub-account/apiRestrictions/ipRestriction/thirdPartyList': 1,
|
||
'sub-account/transaction-statistics': 0.40002, # Weight(UID): 60 => cost = 0.006667 * 60 = 0.40002
|
||
'sub-account/subAccountApi/ipRestriction': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'managed-subaccount/asset': 0.1,
|
||
'managed-subaccount/accountSnapshot': 240,
|
||
'managed-subaccount/queryTransLogForInvestor': 0.1,
|
||
'managed-subaccount/queryTransLogForTradeParent': 0.40002, # Weight(UID): 60 => cost = 0.006667 * 60 = 0.40002
|
||
'managed-subaccount/fetch-future-asset': 0.40002, # Weight(UID): 60 => cost = 0.006667 * 60 = 0.40002
|
||
'managed-subaccount/marginAsset': 0.1,
|
||
'managed-subaccount/info': 0.40002, # Weight(UID): 60 => cost = 0.006667 * 60 = 0.40002
|
||
'managed-subaccount/deposit/address': 0.006667, # Weight(UID): 1 => cost = 0.006667 * 1 = 0.006667
|
||
'managed-subaccount/query-trans-log': 0.40002,
|
||
# lending endpoints
|
||
'lending/daily/product/list': 0.1,
|
||
'lending/daily/userLeftQuota': 0.1,
|
||
'lending/daily/userRedemptionQuota': 0.1,
|
||
'lending/daily/token/position': 0.1,
|
||
'lending/union/account': 0.1,
|
||
'lending/union/purchaseRecord': 0.1,
|
||
'lending/union/redemptionRecord': 0.1,
|
||
'lending/union/interestHistory': 0.1,
|
||
'lending/project/list': 0.1,
|
||
'lending/project/position/list': 0.1,
|
||
# eth-staking
|
||
'eth-staking/eth/history/stakingHistory': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/eth/history/redemptionHistory': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/eth/history/rewardsHistory': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/eth/quota': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/eth/history/rateHistory': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/account': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/wbeth/history/wrapHistory': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/wbeth/history/unwrapHistory': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/eth/history/wbethRewardsHistory': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'sol-staking/sol/history/stakingHistory': 15,
|
||
'sol-staking/sol/history/redemptionHistory': 15,
|
||
'sol-staking/sol/history/bnsolRewardsHistory': 15,
|
||
'sol-staking/sol/history/rateHistory': 15,
|
||
'sol-staking/account': 15,
|
||
'sol-staking/sol/quota': 15,
|
||
# mining endpoints
|
||
'mining/pub/algoList': 0.1,
|
||
'mining/pub/coinList': 0.1,
|
||
'mining/worker/detail': 0.5, # Weight(IP): 5 => cost = 0.1 * 5 = 0.5
|
||
'mining/worker/list': 0.5,
|
||
'mining/payment/list': 0.5,
|
||
'mining/statistics/user/status': 0.5,
|
||
'mining/statistics/user/list': 0.5,
|
||
'mining/payment/uid': 0.5,
|
||
# liquid swap endpoints
|
||
'bswap/pools': 0.1,
|
||
'bswap/liquidity': {'cost': 0.1, 'noPoolId': 1},
|
||
'bswap/liquidityOps': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'bswap/quote': 1.00005, # Weight(UID): 150 => cost = 0.006667 * 150 = 1.00005
|
||
'bswap/swap': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'bswap/poolConfigure': 1.00005, # Weight(UID): 150 => cost = 0.006667 * 150 = 1.00005
|
||
'bswap/addLiquidityPreview': 1.00005, # Weight(UID): 150 => cost = 0.006667 * 150 = 1.00005
|
||
'bswap/removeLiquidityPreview': 1.00005, # Weight(UID): 150 => cost = 0.006667 * 150 = 1.00005
|
||
'bswap/unclaimedRewards': 6.667, # Weight(UID): 1000 => cost = 0.006667 * 1000 = 6.667
|
||
'bswap/claimedHistory': 6.667, # Weight(UID): 1000 => cost = 0.006667 * 1000 = 6.667
|
||
# leveraged token endpoints
|
||
'blvt/tokenInfo': 0.1,
|
||
'blvt/subscribe/record': 0.1,
|
||
'blvt/redeem/record': 0.1,
|
||
'blvt/userLimit': 0.1,
|
||
# broker api TODO(NOT IN DOCS)
|
||
'apiReferral/ifNewUser': 1,
|
||
'apiReferral/customization': 1,
|
||
'apiReferral/userCustomization': 1,
|
||
'apiReferral/rebate/recentRecord': 1,
|
||
'apiReferral/rebate/historicalRecord': 1,
|
||
'apiReferral/kickback/recentRecord': 1,
|
||
'apiReferral/kickback/historicalRecord': 1,
|
||
# brokerage API TODO https://binance-docs.github.io/Brokerage-API/General/ does not state ratelimits
|
||
'broker/subAccountApi': 1,
|
||
'broker/subAccount': 1,
|
||
'broker/subAccountApi/commission/futures': 1,
|
||
'broker/subAccountApi/commission/coinFutures': 1,
|
||
'broker/info': 1,
|
||
'broker/transfer': 1,
|
||
'broker/transfer/futures': 1,
|
||
'broker/rebate/recentRecord': 1,
|
||
'broker/rebate/historicalRecord': 1,
|
||
'broker/subAccount/bnbBurn/status': 1,
|
||
'broker/subAccount/depositHist': 1,
|
||
'broker/subAccount/spotSummary': 1,
|
||
'broker/subAccount/marginSummary': 1,
|
||
'broker/subAccount/futuresSummary': 1,
|
||
'broker/rebate/futures/recentRecord': 1,
|
||
'broker/subAccountApi/ipRestriction': 1,
|
||
'broker/universalTransfer': 1,
|
||
# v2 not supported yet
|
||
# GET /sapi/v2/broker/subAccount/futuresSummary
|
||
'account/apiRestrictions': 0.1,
|
||
# c2c / p2p
|
||
'c2c/orderMatch/listUserOrderHistory': 0.1,
|
||
# nft endpoints
|
||
'nft/history/transactions': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'nft/history/deposit': 20.001,
|
||
'nft/history/withdraw': 20.001,
|
||
'nft/user/getAsset': 20.001,
|
||
'pay/transactions': 20.001,
|
||
'giftcard/verify': 0.1,
|
||
'giftcard/cryptography/rsa-public-key': 0.1,
|
||
'giftcard/buyCode/token-limit': 0.1,
|
||
'algo/spot/openOrders': 0.1,
|
||
'algo/spot/historicalOrders': 0.1,
|
||
'algo/spot/subOrders': 0.1,
|
||
'algo/futures/openOrders': 0.1,
|
||
'algo/futures/historicalOrders': 0.1,
|
||
'algo/futures/subOrders': 0.1,
|
||
'portfolio/account': 0.1,
|
||
'portfolio/collateralRate': 5,
|
||
'portfolio/pmLoan': 3.3335,
|
||
'portfolio/interest-history': 0.6667,
|
||
'portfolio/asset-index-price': 0.1,
|
||
'portfolio/repay-futures-switch': 3, # Weight(IP): 30 => cost = 0.1 * 30 = 3
|
||
'portfolio/margin-asset-leverage': 5, # Weight(IP): 50 => cost = 0.1 * 50 = 5
|
||
'portfolio/balance': 2,
|
||
'portfolio/negative-balance-exchange-record': 2,
|
||
'portfolio/pmloan-history': 5,
|
||
'portfolio/earn-asset-balance': 150, # Weight(IP): 1500 => cost = 0.1 * 1500 = 150
|
||
# staking
|
||
'staking/productList': 0.1,
|
||
'staking/position': 0.1,
|
||
'staking/stakingRecord': 0.1,
|
||
'staking/personalLeftQuota': 0.1,
|
||
'lending/auto-invest/target-asset/list': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/target-asset/roi/list': 0.1,
|
||
'lending/auto-invest/all/asset': 0.1,
|
||
'lending/auto-invest/source-asset/list': 0.1,
|
||
'lending/auto-invest/plan/list': 0.1,
|
||
'lending/auto-invest/plan/id': 0.1,
|
||
'lending/auto-invest/history/list': 0.1,
|
||
'lending/auto-invest/index/info': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/index/user-summary': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/one-off/status': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/redeem/history': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/rebalance/history': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
# simple earn
|
||
'simple-earn/flexible/list': 15,
|
||
'simple-earn/locked/list': 15,
|
||
'simple-earn/flexible/personalLeftQuota': 15,
|
||
'simple-earn/locked/personalLeftQuota': 15,
|
||
'simple-earn/flexible/subscriptionPreview': 15,
|
||
'simple-earn/locked/subscriptionPreview': 15,
|
||
'simple-earn/flexible/history/rateHistory': 15,
|
||
'simple-earn/flexible/position': 15,
|
||
'simple-earn/locked/position': 15,
|
||
'simple-earn/account': 15,
|
||
'simple-earn/flexible/history/subscriptionRecord': 15,
|
||
'simple-earn/locked/history/subscriptionRecord': 15,
|
||
'simple-earn/flexible/history/redemptionRecord': 15,
|
||
'simple-earn/locked/history/redemptionRecord': 15,
|
||
'simple-earn/flexible/history/rewardsRecord': 15,
|
||
'simple-earn/locked/history/rewardsRecord': 15,
|
||
'simple-earn/flexible/history/collateralRecord': 0.1,
|
||
# Convert
|
||
'dci/product/list': 0.1,
|
||
'dci/product/positions': 0.1,
|
||
'dci/product/accounts': 0.1,
|
||
},
|
||
'post': {
|
||
'asset/dust': 0.06667, # Weight(UID): 10 => cost = 0.006667 * 10 = 0.06667
|
||
'asset/dust-btc': 0.1,
|
||
'asset/transfer': 6.0003, # Weight(UID): 900 => cost = 0.006667 * 900 = 6.0003
|
||
'asset/get-funding-asset': 0.1,
|
||
'asset/convert-transfer': 0.033335,
|
||
'account/disableFastWithdrawSwitch': 0.1,
|
||
'account/enableFastWithdrawSwitch': 0.1,
|
||
# 'account/apiRestrictions/ipRestriction': 1, discontinued
|
||
# 'account/apiRestrictions/ipRestriction/ipList': 1, discontinued
|
||
'capital/withdraw/apply': 4.0002, # Weight(UID): 600 => cost = 0.006667 * 600 = 4.0002
|
||
'capital/contract/convertible-coins': 4.0002,
|
||
'capital/deposit/credit-apply': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'margin/borrow-repay': 20.001,
|
||
'margin/transfer': 4.0002,
|
||
'margin/loan': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'margin/repay': 20.001,
|
||
'margin/order': 0.040002, # Weight(UID): 6 => cost = 0.006667 * 6 = 0.040002
|
||
'margin/order/oco': 0.040002,
|
||
'margin/dust': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'margin/exchange-small-liability': 20.001,
|
||
# 'margin/isolated/create': 1, discontinued
|
||
'margin/isolated/transfer': 4.0002, # Weight(UID): 600 => cost = 0.006667 * 600 = 4.0002
|
||
'margin/isolated/account': 2.0001, # Weight(UID): 300 => cost = 0.006667 * 300 = 2.0001
|
||
'margin/max-leverage': 300, # Weight(IP): 3000 => cost = 0.1 * 3000 = 300
|
||
'bnbBurn': 0.1,
|
||
'sub-account/virtualSubAccount': 0.1,
|
||
'sub-account/margin/transfer': 4.0002, # Weight(UID): 600 => cost = 0.006667 * 600 = 4.0002
|
||
'sub-account/margin/enable': 0.1,
|
||
'sub-account/futures/enable': 0.1,
|
||
'sub-account/futures/transfer': 0.1,
|
||
'sub-account/futures/internalTransfer': 0.1,
|
||
'sub-account/transfer/subToSub': 0.1,
|
||
'sub-account/transfer/subToMaster': 0.1,
|
||
'sub-account/universalTransfer': 0.1,
|
||
'sub-account/options/enable': 0.1,
|
||
'managed-subaccount/deposit': 0.1,
|
||
'managed-subaccount/withdraw': 0.1,
|
||
'userDataStream': 0.1,
|
||
'userDataStream/isolated': 0.1,
|
||
'futures/transfer': 0.1,
|
||
# lending
|
||
'lending/customizedFixed/purchase': 0.1,
|
||
'lending/daily/purchase': 0.1,
|
||
'lending/daily/redeem': 0.1,
|
||
# liquid swap endpoints
|
||
'bswap/liquidityAdd': 60, # Weight(UID): 1000 + (Additional: 1 request every 3 seconds = 0.333 requests per second) => cost = ( 1000 / rateLimit ) / 0.333 = 60.0000006
|
||
'bswap/liquidityRemove': 60, # Weight(UID): 1000 + (Additional: 1 request every three seconds)
|
||
'bswap/swap': 60, # Weight(UID): 1000 + (Additional: 1 request every three seconds)
|
||
'bswap/claimRewards': 6.667, # Weight(UID): 1000 => cost = 0.006667 * 1000 = 6.667
|
||
# leveraged token endpoints
|
||
'blvt/subscribe': 0.1,
|
||
'blvt/redeem': 0.1,
|
||
# brokerage API TODO: NO MENTION OF RATELIMITS IN BROKERAGE DOCS
|
||
'apiReferral/customization': 1,
|
||
'apiReferral/userCustomization': 1,
|
||
'apiReferral/rebate/historicalRecord': 1,
|
||
'apiReferral/kickback/historicalRecord': 1,
|
||
'broker/subAccount': 1,
|
||
'broker/subAccount/margin': 1,
|
||
'broker/subAccount/futures': 1,
|
||
'broker/subAccountApi': 1,
|
||
'broker/subAccountApi/permission': 1,
|
||
'broker/subAccountApi/commission': 1,
|
||
'broker/subAccountApi/commission/futures': 1,
|
||
'broker/subAccountApi/commission/coinFutures': 1,
|
||
'broker/transfer': 1,
|
||
'broker/transfer/futures': 1,
|
||
'broker/rebate/historicalRecord': 1,
|
||
'broker/subAccount/bnbBurn/spot': 1,
|
||
'broker/subAccount/bnbBurn/marginInterest': 1,
|
||
'broker/subAccount/blvt': 1,
|
||
'broker/subAccountApi/ipRestriction': 1,
|
||
'broker/subAccountApi/ipRestriction/ipList': 1,
|
||
'broker/universalTransfer': 1,
|
||
'broker/subAccountApi/permission/universalTransfer': 1,
|
||
'broker/subAccountApi/permission/vanillaOptions': 1,
|
||
#
|
||
'giftcard/createCode': 0.1,
|
||
'giftcard/redeemCode': 0.1,
|
||
'giftcard/buyCode': 0.1,
|
||
'algo/spot/newOrderTwap': 20.001,
|
||
'algo/futures/newOrderVp': 20.001,
|
||
'algo/futures/newOrderTwap': 20.001,
|
||
# staking
|
||
'staking/purchase': 0.1,
|
||
'staking/redeem': 0.1,
|
||
'staking/setAutoStaking': 0.1,
|
||
# eth-staking
|
||
'eth-staking/eth/stake': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/eth/redeem': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'eth-staking/wbeth/wrap': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'sol-staking/sol/stake': 15,
|
||
'sol-staking/sol/redeem': 15,
|
||
# mining endpoints
|
||
'mining/hash-transfer/config': 0.5, # Weight(IP): 5 => cost = 0.1 * 5 = 0.5
|
||
'mining/hash-transfer/config/cancel': 0.5, # Weight(IP): 5 => cost = 0.1 * 5 = 0.5
|
||
'portfolio/repay': 20.001,
|
||
'loan/vip/renew': 40.002, # Weight(UID): 6000 => cost = 0.006667 * 6000 = 40.002
|
||
'loan/vip/borrow': 40.002,
|
||
'loan/borrow': 40.002,
|
||
'loan/repay': 40.002,
|
||
'loan/adjust/ltv': 40.002,
|
||
'loan/customize/margin_call': 40.002,
|
||
'loan/flexible/repay': 40.002, # TODO: Deprecating at 2024-04-24 03:00(UTC)
|
||
'loan/flexible/adjust/ltv': 40.002, # TODO: Deprecating at 2024-04-24 03:00(UTC)
|
||
'loan/vip/repay': 40.002,
|
||
'convert/getQuote': 1.3334, # Weight(UID): 200 => cost = 0.006667 * 200 = 1.3334
|
||
'convert/acceptQuote': 3.3335, # Weight(UID): 500 => cost = 0.006667 * 500 = 3.3335
|
||
'convert/limit/placeOrder': 3.3335, # Weight(UID): 500 => cost = 0.006667 * 500 = 3.3335
|
||
'convert/limit/cancelOrder': 1.3334, # Weight(UID): 200 => cost = 0.006667 * 200 = 1.3334
|
||
'portfolio/auto-collection': 150, # Weight(IP): 1500 => cost = 0.1 * 1500 = 150
|
||
'portfolio/asset-collection': 6, # Weight(IP): 60 => cost = 0.1 * 60 = 6
|
||
'portfolio/bnb-transfer': 150, # Weight(IP): 1500 => cost = 0.1 * 1500 = 150
|
||
'portfolio/repay-futures-switch': 150, # Weight(IP): 1500 => cost = 0.1 * 1500 = 150
|
||
'portfolio/repay-futures-negative-balance': 150, # Weight(IP): 1500 => cost = 0.1 * 1500 = 150
|
||
'portfolio/mint': 20,
|
||
'portfolio/redeem': 20,
|
||
'portfolio/earn-asset-transfer': 150, # Weight(IP): 1500 => cost = 0.1 * 1500 = 150
|
||
'lending/auto-invest/plan/add': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/plan/edit': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/plan/edit-status': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/one-off': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
'lending/auto-invest/redeem': 0.1, # Weight(IP): 1 => cost = 0.1 * 1 = 0.1
|
||
# simple earn
|
||
'simple-earn/flexible/subscribe': 0.1,
|
||
'simple-earn/locked/subscribe': 0.1,
|
||
'simple-earn/flexible/redeem': 0.1,
|
||
'simple-earn/locked/redeem': 0.1,
|
||
'simple-earn/flexible/setAutoSubscribe': 15,
|
||
'simple-earn/locked/setAutoSubscribe': 15,
|
||
'simple-earn/locked/setRedeemOption': 5,
|
||
# convert
|
||
'dci/product/subscribe': 0.1,
|
||
'dci/product/auto_compound/edit': 0.1,
|
||
},
|
||
'put': {
|
||
'userDataStream': 0.1,
|
||
'userDataStream/isolated': 0.1,
|
||
},
|
||
'delete': {
|
||
# 'account/apiRestrictions/ipRestriction/ipList': 1, discontinued
|
||
'margin/openOrders': 0.1,
|
||
'margin/order': 0.006667, # Weight(UID): 1 => cost = 0.006667
|
||
'margin/orderList': 0.006667,
|
||
'margin/isolated/account': 2.0001, # Weight(UID): 300 => cost = 0.006667 * 300 = 2.0001
|
||
'userDataStream': 0.1,
|
||
'userDataStream/isolated': 0.1,
|
||
# brokerage API TODO NO MENTION OF RATELIMIT IN BROKERAGE DOCS
|
||
'broker/subAccountApi': 1,
|
||
'broker/subAccountApi/ipRestriction/ipList': 1,
|
||
'algo/spot/order': 0.1,
|
||
'algo/futures/order': 0.1,
|
||
'sub-account/subAccountApi/ipRestriction/ipList': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
},
|
||
},
|
||
'sapiV2': {
|
||
'get': {
|
||
'eth-staking/account': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'sub-account/futures/account': 0.1,
|
||
'sub-account/futures/accountSummary': 1,
|
||
'sub-account/futures/positionRisk': 0.1,
|
||
'loan/flexible/ongoing/orders': 30, # Weight(IP): 300 => cost = 0.1 * 300 = 30
|
||
'loan/flexible/borrow/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/flexible/repay/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/flexible/ltv/adjustment/history': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/flexible/loanable/data': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'loan/flexible/collateral/data': 40, # Weight(IP): 400 => cost = 0.1 * 400 = 40
|
||
'portfolio/account': 2,
|
||
},
|
||
'post': {
|
||
'eth-staking/eth/stake': 15, # Weight(IP): 150 => cost = 0.1 * 150 = 15
|
||
'sub-account/subAccountApi/ipRestriction': 20.001, # Weight(UID): 3000 => cost = 0.006667 * 3000 = 20.001
|
||
'loan/flexible/borrow': 40.002, # Weight(UID): 6000 => cost = 0.006667 * 6000 = 40.002
|
||
'loan/flexible/repay': 40.002, # Weight(UID): 6000 => cost = 0.006667 * 6000 = 40.002
|
||
'loan/flexible/adjust/ltv': 40.002, # Weight(UID): 6000 => cost = 0.006667 * 6000 = 40.002
|
||
},
|
||
},
|
||
'sapiV3': {
|
||
'get': {
|
||
'sub-account/assets': 0.40002, # Weight(UID): 60 => cost = 0.006667 * 60 = 0.40002
|
||
},
|
||
'post': {
|
||
'asset/getUserAsset': 0.5,
|
||
},
|
||
},
|
||
'sapiV4': {
|
||
'get': {
|
||
'sub-account/assets': 0.40002, # Weight(UID): 60 => cost = 0.006667 * 60 = 0.40002
|
||
},
|
||
},
|
||
'dapiPublic': {
|
||
'get': {
|
||
'ping': 1,
|
||
'time': 1,
|
||
'exchangeInfo': 1,
|
||
'depth': {'cost': 2, 'byLimit': [[50, 2], [100, 5], [500, 10], [1000, 20]]},
|
||
'trades': 5,
|
||
'historicalTrades': 20,
|
||
'aggTrades': 20,
|
||
'premiumIndex': 10,
|
||
'fundingRate': 1,
|
||
'klines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'continuousKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'indexPriceKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'markPriceKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'premiumIndexKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'ticker/24hr': {'cost': 1, 'noSymbol': 40},
|
||
'ticker/price': {'cost': 1, 'noSymbol': 2},
|
||
'ticker/bookTicker': {'cost': 2, 'noSymbol': 5},
|
||
'constituents': 2,
|
||
'openInterest': 1,
|
||
'fundingInfo': 1,
|
||
},
|
||
},
|
||
'dapiData': {
|
||
'get': {
|
||
'delivery-price': 1,
|
||
'openInterestHist': 1,
|
||
'topLongShortAccountRatio': 1,
|
||
'topLongShortPositionRatio': 1,
|
||
'globalLongShortAccountRatio': 1,
|
||
'takerBuySellVol': 1,
|
||
'basis': 1,
|
||
},
|
||
},
|
||
'dapiPrivate': {
|
||
'get': {
|
||
'positionSide/dual': 30,
|
||
'orderAmendment': 1,
|
||
'order': 1,
|
||
'openOrder': 1,
|
||
'openOrders': {'cost': 1, 'noSymbol': 5},
|
||
'allOrders': {'cost': 20, 'noSymbol': 40},
|
||
'balance': 1,
|
||
'account': 5,
|
||
'positionMargin/history': 1,
|
||
'positionRisk': 1,
|
||
'userTrades': {'cost': 20, 'noSymbol': 40},
|
||
'income': 20,
|
||
'leverageBracket': 1,
|
||
'forceOrders': {'cost': 20, 'noSymbol': 50},
|
||
'adlQuantile': 5,
|
||
'commissionRate': 20,
|
||
'income/asyn': 5,
|
||
'income/asyn/id': 5,
|
||
'trade/asyn': 0.5,
|
||
'trade/asyn/id': 0.5,
|
||
'order/asyn': 0.5,
|
||
'order/asyn/id': 0.5,
|
||
'pmExchangeInfo': 0.5, # Weight(IP): 5 => cost = 0.1 * 5 = 0.5
|
||
'pmAccountInfo': 0.5, # Weight(IP): 5 => cost = 0.1 * 5 = 0.5
|
||
},
|
||
'post': {
|
||
'positionSide/dual': 1,
|
||
'order': 4,
|
||
'batchOrders': 5,
|
||
'countdownCancelAll': 10,
|
||
'leverage': 1,
|
||
'marginType': 1,
|
||
'positionMargin': 1,
|
||
'listenKey': 1,
|
||
},
|
||
'put': {
|
||
'listenKey': 1,
|
||
'order': 1,
|
||
'batchOrders': 5,
|
||
},
|
||
'delete': {
|
||
'order': 1,
|
||
'allOpenOrders': 1,
|
||
'batchOrders': 5,
|
||
'listenKey': 1,
|
||
},
|
||
},
|
||
'dapiPrivateV2': {
|
||
'get': {
|
||
'leverageBracket': 1,
|
||
},
|
||
},
|
||
'fapiPublic': {
|
||
'get': {
|
||
'ping': 1,
|
||
'time': 1,
|
||
'exchangeInfo': 1,
|
||
'depth': {'cost': 2, 'byLimit': [[50, 2], [100, 5], [500, 10], [1000, 20]]},
|
||
'trades': 5,
|
||
'historicalTrades': 20,
|
||
'aggTrades': 20,
|
||
'klines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'continuousKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'markPriceKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'indexPriceKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'premiumIndexKlines': {'cost': 1, 'byLimit': [[99, 1], [499, 2], [1000, 5], [10000, 10]]},
|
||
'fundingRate': 1,
|
||
'fundingInfo': 1,
|
||
'premiumIndex': 1,
|
||
'ticker/24hr': {'cost': 1, 'noSymbol': 40},
|
||
'ticker/price': {'cost': 1, 'noSymbol': 2},
|
||
'ticker/bookTicker': {'cost': 1, 'noSymbol': 2},
|
||
'openInterest': 1,
|
||
'indexInfo': 1,
|
||
'assetIndex': {'cost': 1, 'noSymbol': 10},
|
||
'constituents': 2,
|
||
'apiTradingStatus': {'cost': 1, 'noSymbol': 10},
|
||
'lvtKlines': 1,
|
||
'convert/exchangeInfo': 4,
|
||
'insuranceBalance': 1,
|
||
},
|
||
},
|
||
'fapiData': {
|
||
'get': {
|
||
'delivery-price': 1,
|
||
'openInterestHist': 1,
|
||
'topLongShortAccountRatio': 1,
|
||
'topLongShortPositionRatio': 1,
|
||
'globalLongShortAccountRatio': 1,
|
||
'takerlongshortRatio': 1,
|
||
'basis': 1,
|
||
},
|
||
},
|
||
'fapiPrivate': {
|
||
'get': {
|
||
'forceOrders': {'cost': 20, 'noSymbol': 50},
|
||
'allOrders': 5,
|
||
'openOrder': 1,
|
||
'openOrders': {'cost': 1, 'noSymbol': 40},
|
||
'order': 1,
|
||
'account': 5,
|
||
'balance': 5,
|
||
'leverageBracket': 1,
|
||
'positionMargin/history': 1,
|
||
'positionRisk': 5,
|
||
'positionSide/dual': 30,
|
||
'userTrades': 5,
|
||
'income': 30,
|
||
'commissionRate': 20,
|
||
'rateLimit/order': 1,
|
||
'apiTradingStatus': 1,
|
||
'multiAssetsMargin': 30,
|
||
# broker endpoints
|
||
'apiReferral/ifNewUser': 1,
|
||
'apiReferral/customization': 1,
|
||
'apiReferral/userCustomization': 1,
|
||
'apiReferral/traderNum': 1,
|
||
'apiReferral/overview': 1,
|
||
'apiReferral/tradeVol': 1,
|
||
'apiReferral/rebateVol': 1,
|
||
'apiReferral/traderSummary': 1,
|
||
'adlQuantile': 5,
|
||
'pmAccountInfo': 5,
|
||
'orderAmendment': 1,
|
||
'income/asyn': 1000,
|
||
'income/asyn/id': 10,
|
||
'order/asyn': 1000,
|
||
'order/asyn/id': 10,
|
||
'trade/asyn': 1000,
|
||
'trade/asyn/id': 10,
|
||
'feeBurn': 1,
|
||
'symbolConfig': 5,
|
||
'accountConfig': 5,
|
||
'convert/orderStatus': 5,
|
||
},
|
||
'post': {
|
||
'batchOrders': 5,
|
||
'positionSide/dual': 1,
|
||
'positionMargin': 1,
|
||
'marginType': 1,
|
||
'order': 4,
|
||
'leverage': 1,
|
||
'listenKey': 1,
|
||
'countdownCancelAll': 10,
|
||
'multiAssetsMargin': 1,
|
||
# broker endpoints
|
||
'apiReferral/customization': 1,
|
||
'apiReferral/userCustomization': 1,
|
||
'feeBurn': 1,
|
||
'convert/getQuote': 200, # 360 requests per hour
|
||
'convert/acceptQuote': 20,
|
||
},
|
||
'put': {
|
||
'listenKey': 1,
|
||
'order': 1,
|
||
'batchOrders': 5,
|
||
},
|
||
'delete': {
|
||
'batchOrders': 1,
|
||
'order': 1,
|
||
'allOpenOrders': 1,
|
||
'listenKey': 1,
|
||
},
|
||
},
|
||
'fapiPublicV2': {
|
||
'get': {
|
||
'ticker/price': 0,
|
||
},
|
||
},
|
||
'fapiPrivateV2': {
|
||
'get': {
|
||
'account': 1,
|
||
'balance': 1,
|
||
'positionRisk': 1,
|
||
},
|
||
},
|
||
'fapiPublicV3': {
|
||
'get': {},
|
||
},
|
||
'fapiPrivateV3': {
|
||
'get': {
|
||
'account': 1,
|
||
'balance': 1,
|
||
'positionRisk': 1,
|
||
},
|
||
},
|
||
'eapiPublic': {
|
||
'get': {
|
||
'ping': 1,
|
||
'time': 1,
|
||
'exchangeInfo': 1,
|
||
'index': 1,
|
||
'ticker': 5,
|
||
'mark': 5,
|
||
'depth': 1,
|
||
'klines': 1,
|
||
'trades': 5,
|
||
'historicalTrades': 20,
|
||
'exerciseHistory': 3,
|
||
'openInterest': 3,
|
||
},
|
||
},
|
||
'eapiPrivate': {
|
||
'get': {
|
||
'account': 3,
|
||
'position': 5,
|
||
'openOrders': {'cost': 1, 'noSymbol': 40},
|
||
'historyOrders': 3,
|
||
'userTrades': 5,
|
||
'exerciseRecord': 5,
|
||
'bill': 1,
|
||
'income/asyn': 5,
|
||
'income/asyn/id': 5,
|
||
'marginAccount': 3,
|
||
'mmp': 1,
|
||
'countdownCancelAll': 1,
|
||
'order': 1,
|
||
'block/order/orders': 5,
|
||
'block/order/execute': 5,
|
||
'block/user-trades': 5,
|
||
'blockTrades': 5,
|
||
},
|
||
'post': {
|
||
'order': 1,
|
||
'batchOrders': 5,
|
||
'listenKey': 1,
|
||
'mmpSet': 1,
|
||
'mmpReset': 1,
|
||
'countdownCancelAll': 1,
|
||
'countdownCancelAllHeartBeat': 10,
|
||
'block/order/create': 5,
|
||
'block/order/execute': 5,
|
||
},
|
||
'put': {
|
||
'listenKey': 1,
|
||
'block/order/create': 5,
|
||
},
|
||
'delete': {
|
||
'order': 1,
|
||
'batchOrders': 1,
|
||
'allOpenOrders': 1,
|
||
'allOpenOrdersByUnderlying': 1,
|
||
'listenKey': 1,
|
||
'block/order/create': 5,
|
||
},
|
||
},
|
||
'public': {
|
||
# IP(api) request rate limit of 6000 per minute
|
||
# 1 IP(api) => cost = 0.2 =>(1000 / (50 * 0.2)) * 60 = 6000
|
||
'get': {
|
||
'ping': 0.2, # Weight(IP): 1 => cost = 0.2 * 1 = 0.2
|
||
'time': 0.2,
|
||
'depth': {'cost': 1, 'byLimit': [[100, 1], [500, 5], [1000, 10], [5000, 50]]},
|
||
'trades': 2, # Weight(IP): 10 => cost = 0.2 * 10 = 2
|
||
'aggTrades': 0.4,
|
||
'historicalTrades': 2, # Weight(IP): 10 => cost = 0.2 * 10 = 2
|
||
'klines': 0.4,
|
||
'uiKlines': 0.4,
|
||
'ticker/24hr': {'cost': 0.4, 'noSymbol': 16},
|
||
'ticker': {'cost': 0.4, 'noSymbol': 16},
|
||
'ticker/tradingDay': 0.8,
|
||
'ticker/price': {'cost': 0.4, 'noSymbol': 0.8},
|
||
'ticker/bookTicker': {'cost': 0.4, 'noSymbol': 0.8},
|
||
'exchangeInfo': 4, # Weight(IP): 20 => cost = 0.2 * 20 = 4
|
||
'avgPrice': 0.4,
|
||
},
|
||
'put': {
|
||
'userDataStream': 0.4,
|
||
},
|
||
'post': {
|
||
'userDataStream': 0.4,
|
||
},
|
||
'delete': {
|
||
'userDataStream': 0.4,
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
'allOrderList': 4, # oco Weight(IP): 20 => cost = 0.2 * 20 = 4
|
||
'openOrderList': 1.2, # oco Weight(IP): 6 => cost = 0.2 * 6 = 1.2
|
||
'orderList': 0.8, # oco
|
||
'order': 0.8,
|
||
'openOrders': {'cost': 1.2, 'noSymbol': 16},
|
||
'allOrders': 4,
|
||
'account': 4,
|
||
'myTrades': 4,
|
||
'rateLimit/order': 8, # Weight(IP): 40 => cost = 0.2 * 40 = 8
|
||
'myPreventedMatches': 4, # Weight(IP): 20 => cost = 0.2 * 20 = 4
|
||
'myAllocations': 4,
|
||
'account/commission': 4,
|
||
},
|
||
'post': {
|
||
'order/oco': 0.2,
|
||
'orderList/oco': 0.2,
|
||
'orderList/oto': 0.2,
|
||
'orderList/otoco': 0.2,
|
||
'sor/order': 0.2,
|
||
'sor/order/test': 0.2,
|
||
'order': 0.2,
|
||
'order/cancelReplace': 0.2,
|
||
'order/test': 0.2,
|
||
},
|
||
'delete': {
|
||
'openOrders': 0.2,
|
||
'orderList': 0.2, # oco
|
||
'order': 0.2,
|
||
},
|
||
},
|
||
'papi': {
|
||
# IP(papi) request rate limit of 6000 per minute
|
||
# 1 IP(papi) => cost = 0.2 =>(1000 / (50 * 0.2)) * 60 = 6000
|
||
# Order(papi) request rate limit of 1200 per minute
|
||
# 1 Order(papi) => cost = 1 =>(1000 / (50 * 1)) * 60 = 1200
|
||
'get': {
|
||
'ping': 0.2,
|
||
'um/order': 1,
|
||
'um/openOrder': 1,
|
||
'um/openOrders': {'cost': 1, 'noSymbol': 40},
|
||
'um/allOrders': 5,
|
||
'cm/order': 1,
|
||
'cm/openOrder': 1,
|
||
'cm/openOrders': {'cost': 1, 'noSymbol': 40},
|
||
'cm/allOrders': 20,
|
||
'um/conditional/openOrder': 1,
|
||
'um/conditional/openOrders': {'cost': 1, 'noSymbol': 40},
|
||
'um/conditional/orderHistory': 1,
|
||
'um/conditional/allOrders': {'cost': 1, 'noSymbol': 40},
|
||
'cm/conditional/openOrder': 1,
|
||
'cm/conditional/openOrders': {'cost': 1, 'noSymbol': 40},
|
||
'cm/conditional/orderHistory': 1,
|
||
'cm/conditional/allOrders': 40,
|
||
'margin/order': 10,
|
||
'margin/openOrders': 5,
|
||
'margin/allOrders': 100,
|
||
'margin/orderList': 5,
|
||
'margin/allOrderList': 100,
|
||
'margin/openOrderList': 5,
|
||
'margin/myTrades': 5,
|
||
'balance': 4,
|
||
'account': 4,
|
||
'margin/maxBorrowable': 1,
|
||
'margin/maxWithdraw': 1,
|
||
'um/positionRisk': 1,
|
||
'cm/positionRisk': 0.2,
|
||
'um/positionSide/dual': 6,
|
||
'cm/positionSide/dual': 6,
|
||
'um/userTrades': 5,
|
||
'cm/userTrades': 20,
|
||
'um/leverageBracket': 0.2,
|
||
'cm/leverageBracket': 0.2,
|
||
'margin/forceOrders': 1,
|
||
'um/forceOrders': {'cost': 20, 'noSymbol': 50},
|
||
'cm/forceOrders': {'cost': 20, 'noSymbol': 50},
|
||
'um/apiTradingStatus': {'cost': 0.2, 'noSymbol': 2},
|
||
'um/commissionRate': 4,
|
||
'cm/commissionRate': 4,
|
||
'margin/marginLoan': 2,
|
||
'margin/repayLoan': 2,
|
||
'margin/marginInterestHistory': 0.2,
|
||
'portfolio/interest-history': 10,
|
||
'um/income': 6,
|
||
'cm/income': 6,
|
||
'um/account': 1,
|
||
'cm/account': 1,
|
||
'repay-futures-switch': 6,
|
||
'um/adlQuantile': 5,
|
||
'cm/adlQuantile': 5,
|
||
'um/trade/asyn': 300,
|
||
'um/trade/asyn/id': 2,
|
||
'um/order/asyn': 300,
|
||
'um/order/asyn/id': 2,
|
||
'um/income/asyn': 300,
|
||
'um/income/asyn/id': 2,
|
||
'um/orderAmendment': 1,
|
||
'cm/orderAmendment': 1,
|
||
'um/feeBurn': 30,
|
||
'um/accountConfig': 1,
|
||
'um/symbolConfig': 1,
|
||
'cm/accountConfig': 1,
|
||
'cm/symbolConfig': 1,
|
||
'rateLimit/order': 1,
|
||
},
|
||
'post': {
|
||
'um/order': 1,
|
||
'um/conditional/order': 1,
|
||
'cm/order': 1,
|
||
'cm/conditional/order': 1,
|
||
'margin/order': 1,
|
||
'marginLoan': 100,
|
||
'repayLoan': 100,
|
||
'margin/order/oco': 1,
|
||
'um/leverage': 0.2,
|
||
'cm/leverage': 0.2,
|
||
'um/positionSide/dual': 0.2,
|
||
'cm/positionSide/dual': 0.2,
|
||
'auto-collection': 150,
|
||
'bnb-transfer': 150,
|
||
'repay-futures-switch': 150,
|
||
'repay-futures-negative-balance': 150,
|
||
'listenKey': 0.2,
|
||
'asset-collection': 6,
|
||
'margin/repay-debt': 3000,
|
||
'um/feeBurn': 1,
|
||
},
|
||
'put': {
|
||
'listenKey': 0.2,
|
||
'um/order': 1,
|
||
'cm/order': 1,
|
||
},
|
||
'delete': {
|
||
'um/order': 1,
|
||
'um/conditional/order': 1,
|
||
'um/allOpenOrders': 1,
|
||
'um/conditional/allOpenOrders': 1,
|
||
'cm/order': 1,
|
||
'cm/conditional/order': 1,
|
||
'cm/allOpenOrders': 1,
|
||
'cm/conditional/allOpenOrders': 1,
|
||
'margin/order': 2,
|
||
'margin/allOpenOrders': 5,
|
||
'margin/orderList': 2,
|
||
'listenKey': 0.2,
|
||
},
|
||
},
|
||
'papiV2': {
|
||
'get': {
|
||
'um/account': 1,
|
||
},
|
||
},
|
||
},
|
||
'fees': {
|
||
'trading': {
|
||
'feeSide': 'get',
|
||
'tierBased': False,
|
||
'percentage': True,
|
||
'taker': self.parse_number('0.001'),
|
||
'maker': self.parse_number('0.001'),
|
||
},
|
||
'linear': {
|
||
'trading': {
|
||
'feeSide': 'quote',
|
||
'tierBased': True,
|
||
'percentage': True,
|
||
'taker': self.parse_number('0.000500'),
|
||
'maker': self.parse_number('0.000200'),
|
||
'tiers': {
|
||
'taker': [
|
||
[self.parse_number('0'), self.parse_number('0.000400')],
|
||
[self.parse_number('250'), self.parse_number('0.000400')],
|
||
[self.parse_number('2500'), self.parse_number('0.000350')],
|
||
[self.parse_number('7500'), self.parse_number('0.000320')],
|
||
[self.parse_number('22500'), self.parse_number('0.000300')],
|
||
[self.parse_number('50000'), self.parse_number('0.000270')],
|
||
[self.parse_number('100000'), self.parse_number('0.000250')],
|
||
[self.parse_number('200000'), self.parse_number('0.000220')],
|
||
[self.parse_number('400000'), self.parse_number('0.000200')],
|
||
[self.parse_number('750000'), self.parse_number('0.000170')],
|
||
],
|
||
'maker': [
|
||
[self.parse_number('0'), self.parse_number('0.000200')],
|
||
[self.parse_number('250'), self.parse_number('0.000160')],
|
||
[self.parse_number('2500'), self.parse_number('0.000140')],
|
||
[self.parse_number('7500'), self.parse_number('0.000120')],
|
||
[self.parse_number('22500'), self.parse_number('0.000100')],
|
||
[self.parse_number('50000'), self.parse_number('0.000080')],
|
||
[self.parse_number('100000'), self.parse_number('0.000060')],
|
||
[self.parse_number('200000'), self.parse_number('0.000040')],
|
||
[self.parse_number('400000'), self.parse_number('0.000020')],
|
||
[self.parse_number('750000'), self.parse_number('0')],
|
||
],
|
||
},
|
||
},
|
||
},
|
||
'inverse': {
|
||
'trading': {
|
||
'feeSide': 'base',
|
||
'tierBased': True,
|
||
'percentage': True,
|
||
'taker': self.parse_number('0.000500'),
|
||
'maker': self.parse_number('0.000100'),
|
||
'tiers': {
|
||
'taker': [
|
||
[self.parse_number('0'), self.parse_number('0.000500')],
|
||
[self.parse_number('250'), self.parse_number('0.000450')],
|
||
[self.parse_number('2500'), self.parse_number('0.000400')],
|
||
[self.parse_number('7500'), self.parse_number('0.000300')],
|
||
[self.parse_number('22500'), self.parse_number('0.000250')],
|
||
[self.parse_number('50000'), self.parse_number('0.000240')],
|
||
[self.parse_number('100000'), self.parse_number('0.000240')],
|
||
[self.parse_number('200000'), self.parse_number('0.000240')],
|
||
[self.parse_number('400000'), self.parse_number('0.000240')],
|
||
[self.parse_number('750000'), self.parse_number('0.000240')],
|
||
],
|
||
'maker': [
|
||
[self.parse_number('0'), self.parse_number('0.000100')],
|
||
[self.parse_number('250'), self.parse_number('0.000080')],
|
||
[self.parse_number('2500'), self.parse_number('0.000050')],
|
||
[self.parse_number('7500'), self.parse_number('0.0000030')],
|
||
[self.parse_number('22500'), self.parse_number('0')],
|
||
[self.parse_number('50000'), self.parse_number('-0.000050')],
|
||
[self.parse_number('100000'), self.parse_number('-0.000060')],
|
||
[self.parse_number('200000'), self.parse_number('-0.000070')],
|
||
[self.parse_number('400000'), self.parse_number('-0.000080')],
|
||
[self.parse_number('750000'), self.parse_number('-0.000090')],
|
||
],
|
||
},
|
||
},
|
||
},
|
||
'option': {},
|
||
},
|
||
'currencies': {
|
||
'BNFCR': self.safe_currency_structure({'id': 'BNFCR', 'code': 'BNFCR', 'precision': self.parse_number('0.001')}),
|
||
},
|
||
'commonCurrencies': {
|
||
'BCC': 'BCC', # kept for backward-compatibility https://github.com/ccxt/ccxt/issues/4848
|
||
'YOYO': 'YOYOW',
|
||
},
|
||
'precisionMode': TICK_SIZE,
|
||
# exchange-specific options
|
||
'options': {
|
||
'sandboxMode': False,
|
||
'fetchMargins': True,
|
||
'fetchMarkets': {
|
||
'types': [
|
||
'spot', # allows CORS in browsers
|
||
'linear', # allows CORS in browsers
|
||
'inverse', # allows CORS in browsers
|
||
# 'option', # does not allow CORS, enable outside of the browser only
|
||
],
|
||
},
|
||
'loadAllOptions': False,
|
||
'fetchCurrencies': True, # self is a private call and it requires API keys
|
||
# 'fetchTradesMethod': 'publicGetAggTrades', # publicGetTrades, publicGetHistoricalTrades, eapiPublicGetTrades
|
||
# 'repayCrossMarginMethod': 'papiPostRepayLoan', # papiPostMarginRepayDebt
|
||
'defaultTimeInForce': 'GTC', # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
|
||
'defaultType': 'spot', # 'spot', 'future', 'margin', 'delivery', 'option'
|
||
'defaultSubType': None, # 'linear', 'inverse'
|
||
'hasAlreadyAuthenticatedSuccessfully': False,
|
||
'warnOnFetchOpenOrdersWithoutSymbol': True,
|
||
'currencyToPrecisionRoundingMode': TRUNCATE,
|
||
# not an error
|
||
# https://github.com/ccxt/ccxt/issues/11268
|
||
# https://github.com/ccxt/ccxt/pull/11624
|
||
# POST https://fapi.binance.com/fapi/v1/marginType 400 Bad Request
|
||
# binanceusdm
|
||
'throwMarginModeAlreadySet': False,
|
||
'fetchPositions': 'positionRisk', # or 'account' or 'option'
|
||
'recvWindow': 10 * 1000, # 10 sec
|
||
'timeDifference': 0, # the difference between system clock and Binance clock
|
||
'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
|
||
'newOrderRespType': {
|
||
'market': 'FULL', # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
|
||
'limit': 'FULL', # we change it from 'ACK' by default to 'FULL'(returns immediately if limit is not hit)
|
||
},
|
||
'quoteOrderQty': True, # whether market orders support amounts in quote currency
|
||
'broker': {
|
||
'spot': 'x-TKT5PX2F',
|
||
'margin': 'x-TKT5PX2F',
|
||
'future': 'x-cvBPrNm9',
|
||
'delivery': 'x-xcKtGhcu',
|
||
'swap': 'x-cvBPrNm9',
|
||
'option': 'x-xcKtGhcu',
|
||
'inverse': 'x-xcKtGhcu',
|
||
},
|
||
'accountsByType': {
|
||
'main': 'MAIN',
|
||
'spot': 'MAIN',
|
||
'funding': 'FUNDING',
|
||
'margin': 'MARGIN',
|
||
'cross': 'MARGIN',
|
||
'future': 'UMFUTURE', # backwards compatibility
|
||
'delivery': 'CMFUTURE', # backwards compatbility
|
||
'linear': 'UMFUTURE',
|
||
'swap': 'UMFUTURE',
|
||
'inverse': 'CMFUTURE',
|
||
'option': 'OPTION',
|
||
},
|
||
'accountsById': {
|
||
'MAIN': 'spot',
|
||
'FUNDING': 'funding',
|
||
'MARGIN': 'margin',
|
||
'UMFUTURE': 'linear',
|
||
'CMFUTURE': 'inverse',
|
||
'OPTION': 'option',
|
||
},
|
||
'networks': {
|
||
'ERC20': 'ETH',
|
||
'TRC20': 'TRX',
|
||
'BEP2': 'BNB',
|
||
'BEP20': 'BSC',
|
||
'OMNI': 'OMNI',
|
||
'EOS': 'EOS',
|
||
'SPL': 'SOL', # temporarily keep support for SPL(old name)
|
||
'SOL': 'SOL', # we shouldn't rename SOL
|
||
},
|
||
'networksById': {
|
||
'SOL': 'SOL', # temporary fix for SPL definition
|
||
},
|
||
'impliedNetworks': {
|
||
'ETH': {'ERC20': 'ETH'},
|
||
'TRX': {'TRC20': 'TRX'},
|
||
},
|
||
'legalMoney': {
|
||
'MXN': True,
|
||
'UGX': True,
|
||
'SEK': True,
|
||
'CHF': True,
|
||
'VND': True,
|
||
'AED': True,
|
||
'DKK': True,
|
||
'KZT': True,
|
||
'HUF': True,
|
||
'PEN': True,
|
||
'PHP': True,
|
||
'USD': True,
|
||
'TRY': True,
|
||
'EUR': True,
|
||
'NGN': True,
|
||
'PLN': True,
|
||
'BRL': True,
|
||
'ZAR': True,
|
||
'KES': True,
|
||
'ARS': True,
|
||
'RUB': True,
|
||
'AUD': True,
|
||
'NOK': True,
|
||
'CZK': True,
|
||
'GBP': True,
|
||
'UAH': True,
|
||
'GHS': True,
|
||
'HKD': True,
|
||
'CAD': True,
|
||
'INR': True,
|
||
'JPY': True,
|
||
'NZD': True,
|
||
},
|
||
'legalMoneyCurrenciesById': {
|
||
'BUSD': 'USD',
|
||
},
|
||
'defaultWithdrawPrecision': 0.00000001,
|
||
},
|
||
'features': {
|
||
'spot': {
|
||
'sandbox': True,
|
||
'fetchCurrencies': {
|
||
'private': True,
|
||
},
|
||
'createOrder': {
|
||
'marginMode': True,
|
||
'triggerPrice': True,
|
||
'triggerPriceType': None,
|
||
'triggerDirection': False,
|
||
'stopLossPrice': True,
|
||
'takeProfitPrice': True,
|
||
'attachedStopLossTakeProfit': None,
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': True,
|
||
'GTD': False,
|
||
},
|
||
'hedged': True,
|
||
'leverage': False,
|
||
'marketBuyByCost': True,
|
||
'marketBuyRequiresPrice': False,
|
||
'selfTradePrevention': {
|
||
'EXPIRE_MAKER': True,
|
||
'EXPIRE_TAKER': True,
|
||
'EXPIRE_BOTH': True,
|
||
'NONE': True,
|
||
},
|
||
'trailing': False, # todo: self is different from standard trailing https://github.com/binance/binance-spot-api-docs/blob/master/faqs/trailing-stop-faq.md
|
||
'icebergAmount': True,
|
||
},
|
||
'createOrders': None,
|
||
'fetchMyTrades': {
|
||
'marginMode': False,
|
||
'limit': 1000,
|
||
'daysBack': None,
|
||
'untilDays': 1, # days between start-end
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': True,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': True,
|
||
'limit': None,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrders': {
|
||
'marginMode': True,
|
||
'limit': 1000,
|
||
'daysBack': None,
|
||
'untilDays': 10000,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchClosedOrders': {
|
||
'marginMode': True,
|
||
'limit': 1000,
|
||
'daysBack': None,
|
||
'daysBackCanceled': None,
|
||
'untilDays': 10000,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOHLCV': {
|
||
'limit': 1000,
|
||
},
|
||
},
|
||
'forDerivatives': {
|
||
'sandbox': True,
|
||
'createOrder': {
|
||
'marginMode': False,
|
||
'triggerPrice': True,
|
||
'triggerPriceType': {
|
||
'mark': True,
|
||
'last': True,
|
||
'index': False,
|
||
},
|
||
'stopLossPrice': True,
|
||
'takeProfitPrice': True,
|
||
'attachedStopLossTakeProfit': None, # not supported
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': True,
|
||
'GTD': True,
|
||
# 'GTX': True,
|
||
},
|
||
'hedged': True,
|
||
# exchange-supported features
|
||
'selfTradePrevention': True,
|
||
'trailing': True,
|
||
'iceberg': False,
|
||
'leverage': False,
|
||
'marketBuyRequiresPrice': False,
|
||
'marketBuyByCost': True,
|
||
},
|
||
'createOrders': {
|
||
'max': 5,
|
||
},
|
||
'fetchMyTrades': {
|
||
'marginMode': False,
|
||
'daysBack': None,
|
||
'limit': 1000,
|
||
'untilDays': 7,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOrder': {
|
||
'marginMode': False,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': True,
|
||
'limit': 500,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrders': {
|
||
'marginMode': True,
|
||
'limit': 1000,
|
||
'daysBack': 90,
|
||
'untilDays': 7,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchClosedOrders': {
|
||
'marginMode': True,
|
||
'limit': 1000,
|
||
'daysBack': 90,
|
||
'daysBackCanceled': 3,
|
||
'untilDays': 7,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': True,
|
||
},
|
||
'fetchOHLCV': {
|
||
'limit': 1500,
|
||
},
|
||
},
|
||
'swap': {
|
||
'linear': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
'inverse': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
},
|
||
'future': {
|
||
'linear': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
'inverse': {
|
||
'extends': 'forDerivatives',
|
||
},
|
||
},
|
||
},
|
||
'exceptions': {
|
||
'spot': {
|
||
'exact': {
|
||
#
|
||
# 1xxx
|
||
#
|
||
'-1004': OperationFailed, # {"code":-1004,"msg":"Server is busy, please wait and try again"}
|
||
'-1008': OperationFailed, # undocumented, but mentioned: This is sent whenever the servers are overloaded with requests.
|
||
'-1099': AuthenticationError, # {"code":-1099,"msg":"Not found, authenticated, or authorized"}
|
||
'-1108': BadRequest, # undocumented, but mentioned: This error will occur if a value to a parameter being sent was too large, potentially causing overflow
|
||
'-1131': BadRequest, # {"code":-1131,"msg":"recvWindow must be less than 60000"}
|
||
'-1134': BadRequest, # strategyType was less than 1000000.
|
||
'-1135': BadRequest, # undocumented, but mentioned: This error code will occur if a parameter requiring a JSON object is invalid.
|
||
'-1145': BadRequest, # cancelRestrictions has to be either ONLY_NEW or ONLY_PARTIALLY_FILLED.
|
||
'-1151': BadSymbol, # Symbol is present multiple times in the list.
|
||
#
|
||
# 2xxx
|
||
#
|
||
'-2008': AuthenticationError, # undocumented, Invalid Api-Key ID
|
||
'-2016': OperationRejected, # {"code":-2016,"msg":"No trading window could be found for the symbol. Try ticker/24hrs instead."}
|
||
'-2021': BadResponse, # This code is sent when either the cancellation of the order failed or the new order placement failed but not both.
|
||
'-2022': BadResponse, # This code is sent when both the cancellation of the order failed and the new order placement failed.
|
||
'-2026': InvalidOrder, # Order was canceled or expired with no executed qty over 90 days ago and has been archived.
|
||
#
|
||
# 3xxx(these errors are available only for spot atm)
|
||
#
|
||
'-3000': OperationFailed, # {"code":-3000,"msg":"Internal server error."}
|
||
'-3001': AuthenticationError, # {"code":-3001,"msg":"Please enable 2FA first."}
|
||
'-3002': BadSymbol, # {"code":-3002,"msg":"We don't have self asset."}
|
||
'-3003': BadRequest, # {"code":-3003,"msg":"Margin account does not exist."}
|
||
'-3004': OperationRejected, # {"code":-3004,"msg":"Trade not allowed."}
|
||
'-3005': BadRequest, # {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."}
|
||
'-3006': BadRequest, # {"code":-3006,"msg":"Your borrow amount has exceed maximum borrow amount."}
|
||
'-3007': OperationFailed, # {"code":-3007,"msg":"You have pending transaction, please try again later.."}
|
||
'-3008': BadRequest, # {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."}
|
||
'-3009': OperationRejected, # {"code":-3009,"msg":"This asset are not allowed to transfer into margin account currently."}
|
||
'-3010': BadRequest, # {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."}
|
||
'-3011': BadRequest, # {"code":-3011,"msg":"Your input date is invalid."}
|
||
'-3012': OperationRejected, # {"code":-3012,"msg":"Borrow is banned for self asset."}
|
||
'-3013': BadRequest, # {"code":-3013,"msg":"Borrow amount less than minimum borrow amount."}
|
||
'-3014': AccountSuspended, # {"code":-3014,"msg":"Borrow is banned for self account."}
|
||
'-3015': BadRequest, # {"code":-3015,"msg":"Repay amount exceeds borrow amount."}
|
||
'-3016': BadRequest, # {"code":-3016,"msg":"Repay amount less than minimum repay amount."}
|
||
'-3017': OperationRejected, # {"code":-3017,"msg":"This asset are not allowed to transfer into margin account currently."}
|
||
'-3018': AccountSuspended, # {"code":-3018,"msg":"Transferring in has been banned for self account."}
|
||
'-3019': AccountSuspended, # {"code":-3019,"msg":"Transferring out has been banned for self account."}
|
||
'-3020': BadRequest, # {"code":-3020,"msg":"Transfer out amount exceeds max amount."}
|
||
'-3021': BadRequest, # {"code":-3021,"msg":"Margin account are not allowed to trade self trading pair."}
|
||
'-3022': AccountSuspended, # {"code":-3022,"msg":"You account's trading is banned."}
|
||
'-3023': OperationRejected, # {"code":-3023,"msg":"You can't transfer out/place order under current margin level."}
|
||
'-3024': OperationRejected, # {"code":-3024,"msg":"The unpaid debt is too small after self repayment."}
|
||
'-3025': BadRequest, # {"code":-3025,"msg":"Your input date is invalid."}
|
||
'-3026': BadRequest, # {"code":-3026,"msg":"Your input param is invalid."}
|
||
'-3027': BadSymbol, # {"code":-3027,"msg":"Not a valid margin asset."}
|
||
'-3028': BadSymbol, # {"code":-3028,"msg":"Not a valid margin pair."}
|
||
'-3029': OperationFailed, # {"code":-3029,"msg":"Transfer failed."}
|
||
'-3036': AccountSuspended, # {"code":-3036,"msg":"This account is not allowed to repay."}
|
||
'-3037': OperationFailed, # {"code":-3037,"msg":"PNL is clearing. Wait a second."}
|
||
'-3038': BadRequest, # {"code":-3038,"msg":"Listen key not found."}
|
||
'-3041': InsufficientFunds, # {"code":-3041,"msg":"Balance is not enough"}
|
||
'-3042': BadRequest, # {"code":-3042,"msg":"PriceIndex not available for self margin pair."}
|
||
'-3043': PermissionDenied, # {"code":-3043,"msg":"Transferring in not allowed."}
|
||
'-3044': OperationFailed, # {"code":-3044,"msg":"System busy."}
|
||
'-3045': OperationRejected, # {"code":-3045,"msg":"The system doesn't have enough asset now."}
|
||
'-3999': PermissionDenied, # {"code":-3999,"msg":"This function is only available for invited users."}
|
||
#
|
||
# 4xxx(different from contract markets)
|
||
#
|
||
'-4000': ExchangeError, # override commons
|
||
'-4001': BadRequest, # {"code":-4001 ,"msg":"Invalid operation."}
|
||
'-4002': BadRequest, # {"code":-4002 ,"msg":"Invalid get."}
|
||
'-4003': BadRequest, # {"code":-4003 ,"msg":"Your input email is invalid."}
|
||
'-4004': AuthenticationError, # {"code":-4004,"msg":"You don't login or auth."}
|
||
'-4005': RateLimitExceeded, # {"code":-4005 ,"msg":"Too many new requests."}
|
||
'-4006': BadRequest, # {"code":-4006 ,"msg":"Support main account only."}
|
||
'-4007': PermissionDenied, # {"code":-4007 ,"msg":"Address validation is not passed."}
|
||
'-4008': PermissionDenied, # {"code":-4008 ,"msg":"Address tag validation is not passed."}
|
||
'-4009': ExchangeError, # undocumented
|
||
'-4010': PermissionDenied, # {"code":-4010 ,"msg":"White list mail has been confirmed."} # [TODO] possible bug: it should probably be "has not been confirmed"
|
||
'-4011': BadRequest, # {"code":-4011 ,"msg":"White list mail is invalid."}
|
||
'-4012': PermissionDenied, # {"code":-4012 ,"msg":"White list is not opened."}
|
||
'-4013': AuthenticationError, # {"code":-4013 ,"msg":"2FA is not opened."}
|
||
'-4014': OperationRejected, # {"code":-4014 ,"msg":"Withdraw is not allowed within 2 min login."}
|
||
'-4015': PermissionDenied, # {"code":-4015 ,"msg":"Withdraw is limited."}
|
||
'-4016': PermissionDenied, # {"code":-4016 ,"msg":"Within 24 hours after password modification, withdrawal is prohibited."}
|
||
'-4017': PermissionDenied, # {"code":-4017 ,"msg":"Within 24 hours after the release of 2FA, withdrawal is prohibited."}
|
||
'-4018': BadSymbol, # {"code":-4018,"msg":"We don't have self asset."}
|
||
'-4019': BadRequest, # {"code":-4019,"msg":"Current asset is not open for withdrawal."}
|
||
'-4020': ExchangeError, # override commons
|
||
'-4021': BadRequest, # {"code":-4021,"msg":"Asset withdrawal must be an %s multiple of %s."}
|
||
'-4022': BadRequest, # {"code":-4022,"msg":"Not less than the minimum pick-up quantity %s."}
|
||
'-4023': OperationRejected, # {"code":-4023,"msg":"Within 24 hours, the withdrawal exceeds the maximum amount."}
|
||
'-4024': InsufficientFunds, # {"code":-4024,"msg":"You don't have self asset."}
|
||
'-4025': InsufficientFunds, # {"code":-4025,"msg":"The number of hold asset is less than zero."}
|
||
'-4026': InsufficientFunds, # {"code":-4026,"msg":"You have insufficient balance."}
|
||
'-4027': OperationFailed, # {"code":-4027,"msg":"Failed to obtain tranId."}
|
||
'-4028': BadRequest, # {"code":-4028,"msg":"The amount of withdrawal must be greater than the Commission."}
|
||
'-4029': BadRequest, # {"code":-4029,"msg":"The withdrawal record does not exist."}
|
||
'-4030': BadResponse, # {"code":-4030,"msg":"Confirmation of successful asset withdrawal. [TODO] possible bug in docs"}
|
||
'-4031': OperationFailed, # {"code":-4031,"msg":"Cancellation failed."}
|
||
'-4032': OperationRejected, # {"code":-4032,"msg":"Withdraw verification exception."}
|
||
'-4033': BadRequest, # {"code":-4033,"msg":"Illegal address."}
|
||
'-4034': OperationRejected, # {"code":-4034,"msg":"The address is suspected of fake."}
|
||
'-4035': PermissionDenied, # {"code":-4035,"msg":"This address is not on the whitelist. Please join and try again."}
|
||
'-4036': PermissionDenied, # {"code":-4036,"msg":"The new address needs to be withdrawn in {0} hours."}
|
||
'-4037': OperationFailed, # {"code":-4037,"msg":"Re-sending Mail failed."}
|
||
'-4038': OperationFailed, # {"code":-4038,"msg":"Please try again in 5 minutes."}
|
||
'-4039': PermissionDenied, # {"code":-4039,"msg":"The user does not exist."}
|
||
'-4040': OperationRejected, # {"code":-4040,"msg":"This address not charged."}
|
||
'-4041': OperationFailed, # {"code":-4041,"msg":"Please try again in one minute."}
|
||
'-4042': OperationRejected, # {"code":-4042,"msg":"This asset cannot get deposit address again."}
|
||
'-4043': OperationRejected, # {"code":-4043,"msg":"More than 100 recharge addresses were used in 24 hours."}
|
||
'-4044': PermissionDenied, # {"code":-4044,"msg":"This is a blacklist country."}
|
||
'-4045': OperationFailed, # {"code":-4045,"msg":"Failure to acquire assets."}
|
||
'-4046': AuthenticationError, # {"code":-4046,"msg":"Agreement not confirmed."}
|
||
'-4047': BadRequest, # {"code":-4047,"msg":"Time interval must be within 0-90 days"}
|
||
'-4048': ExchangeError, # override commons
|
||
'-4049': ExchangeError, # override commons
|
||
'-4050': ExchangeError, # override commons
|
||
'-4051': ExchangeError, # override commons
|
||
'-4052': ExchangeError, # override commons
|
||
'-4053': ExchangeError, # override commons
|
||
'-4054': ExchangeError, # override commons
|
||
'-4055': ExchangeError, # override commons
|
||
'-4056': ExchangeError, # override commons
|
||
'-4057': ExchangeError, # override commons
|
||
'-4058': ExchangeError, # override commons
|
||
'-4059': ExchangeError, # override commons
|
||
'-4060': OperationFailed, # As your deposit has not reached the required block confirmations, we have temporarily locked {0} asset
|
||
'-4061': ExchangeError, # override commons
|
||
'-4062': ExchangeError, # override commons
|
||
'-4063': ExchangeError, # override commons
|
||
'-4064': ExchangeError, # override commons
|
||
'-4065': ExchangeError, # override commons
|
||
'-4066': ExchangeError, # override commons
|
||
'-4067': ExchangeError, # override commons
|
||
'-4068': ExchangeError, # override commons
|
||
'-4069': ExchangeError, # override commons
|
||
'-4070': ExchangeError, # override commons
|
||
'-4071': ExchangeError, # override commons
|
||
'-4072': ExchangeError, # override commons
|
||
'-4073': ExchangeError, # override commons
|
||
'-4074': ExchangeError, # override commons
|
||
'-4075': ExchangeError, # override commons
|
||
'-4076': ExchangeError, # override commons
|
||
'-4077': ExchangeError, # override commons
|
||
'-4078': ExchangeError, # override commons
|
||
'-4079': ExchangeError, # override commons
|
||
'-4080': ExchangeError, # override commons
|
||
'-4081': ExchangeError, # override commons
|
||
'-4082': ExchangeError, # override commons
|
||
'-4083': ExchangeError, # override commons
|
||
'-4084': ExchangeError, # override commons
|
||
'-4085': ExchangeError, # override commons
|
||
'-4086': ExchangeError, # override commons
|
||
'-4087': ExchangeError, # override commons
|
||
'-4088': ExchangeError, # override commons
|
||
'-4089': ExchangeError, # override commons
|
||
'-4091': ExchangeError, # override commons
|
||
'-4092': ExchangeError, # override commons
|
||
'-4093': ExchangeError, # override commons
|
||
'-4094': ExchangeError, # override commons
|
||
'-4095': ExchangeError, # override commons
|
||
'-4096': ExchangeError, # override commons
|
||
'-4097': ExchangeError, # override commons
|
||
'-4098': ExchangeError, # override commons
|
||
'-4099': ExchangeError, # override commons
|
||
'-4101': ExchangeError, # override commons
|
||
'-4102': ExchangeError, # override commons
|
||
'-4103': ExchangeError, # override commons
|
||
'-4104': ExchangeError, # override commons
|
||
'-4105': ExchangeError, # override commons
|
||
'-4106': ExchangeError, # override commons
|
||
'-4107': ExchangeError, # override commons
|
||
'-4108': ExchangeError, # override commons
|
||
'-4109': ExchangeError, # override commons
|
||
'-4110': ExchangeError, # override commons
|
||
'-4112': ExchangeError, # override commons
|
||
'-4113': ExchangeError, # override commons
|
||
'-4114': ExchangeError, # override commons
|
||
'-4115': ExchangeError, # override commons
|
||
'-4116': ExchangeError, # override commons
|
||
'-4117': ExchangeError, # override commons
|
||
'-4118': ExchangeError, # override commons
|
||
'-4119': ExchangeError, # override commons
|
||
'-4120': ExchangeError, # override commons
|
||
'-4121': ExchangeError, # override commons
|
||
'-4122': ExchangeError, # override commons
|
||
'-4123': ExchangeError, # override commons
|
||
'-4124': ExchangeError, # override commons
|
||
'-4125': ExchangeError, # override commons
|
||
'-4126': ExchangeError, # override commons
|
||
'-4127': ExchangeError, # override commons
|
||
'-4128': ExchangeError, # override commons
|
||
'-4129': ExchangeError, # override commons
|
||
'-4130': ExchangeError, # override commons
|
||
'-4131': ExchangeError, # override commons
|
||
'-4132': ExchangeError, # override commons
|
||
'-4133': ExchangeError, # override commons
|
||
'-4134': ExchangeError, # override commons
|
||
'-4135': ExchangeError, # override commons
|
||
'-4136': ExchangeError, # override commons
|
||
'-4137': ExchangeError, # override commons
|
||
'-4138': ExchangeError, # override commons
|
||
'-4139': ExchangeError, # override commons
|
||
'-4141': ExchangeError, # override commons
|
||
'-4142': ExchangeError, # override commons
|
||
'-4143': ExchangeError, # override commons
|
||
'-4144': ExchangeError, # override commons
|
||
'-4145': ExchangeError, # override commons
|
||
'-4146': ExchangeError, # override commons
|
||
'-4147': ExchangeError, # override commons
|
||
'-4148': ExchangeError, # override commons
|
||
'-4149': ExchangeError, # override commons
|
||
'-4150': ExchangeError, # override commons
|
||
#
|
||
# 5xxx
|
||
#
|
||
'-5001': BadRequest, # Don't allow transfer to micro assets.
|
||
'-5002': InsufficientFunds, # You have insufficient balance.
|
||
'-5003': InsufficientFunds, # You don't have self asset.
|
||
'-5004': OperationRejected, # The residual balances of %s have exceeded 0.001BTC, Please re-choose.
|
||
'-5005': OperationRejected, # The residual balances of %s is too low, Please re-choose.
|
||
'-5006': OperationRejected, # Only transfer once in 24 hours.
|
||
'-5007': BadRequest, # Quantity must be greater than zero.
|
||
'-5008': OperationRejected, # Insufficient amount of returnable assets.
|
||
'-5009': BadSymbol, # Product does not exist.
|
||
'-5010': OperationFailed, # Asset transfer fail.
|
||
'-5011': BadRequest, # future account not exists.
|
||
'-5012': OperationFailed, # Asset transfer is in pending.
|
||
'-5013': InsufficientFunds, # {"code":-5013,"msg":"Asset transfer failed: insufficient balance""} # undocumented
|
||
'-5021': BadRequest, # This parent sub have no relation
|
||
'-5022': BadRequest, # future account or sub relation not exists.
|
||
#
|
||
# 6xxx
|
||
#
|
||
'-6001': BadSymbol, # Daily product not exists.
|
||
'-6003': PermissionDenied, # Product not exist or you don't have permission
|
||
'-6004': BadRequest, # Product not in purchase status
|
||
'-6005': BadRequest, # Smaller than min purchase limit
|
||
'-6006': BadRequest, # Redeem amount error
|
||
'-6007': OperationRejected, # Not in redeem time
|
||
'-6008': OperationRejected, # Product not in redeem status
|
||
'-6009': RateLimitExceeded, # Request frequency too high
|
||
'-6011': OperationRejected, # Exceeding the maximum num allowed to purchase per user
|
||
'-6012': InsufficientFunds, # Balance not enough
|
||
'-6013': BadResponse, # Purchasing failed
|
||
'-6014': OperationRejected, # Exceed up-limit allowed to purchased
|
||
'-6015': BadRequest, # Empty request body
|
||
'-6016': BadRequest, # Parameter err
|
||
'-6017': PermissionDenied, # Not in whitelist
|
||
'-6018': InsufficientFunds, # Asset not enough
|
||
'-6019': OperationRejected, # Need confirm
|
||
'-6020': BadRequest, # Project not exists
|
||
#
|
||
# 7xxx
|
||
#
|
||
'-7001': BadRequest, # Date range is not supported.
|
||
'-7002': BadRequest, # Data request type is not supported.
|
||
#
|
||
# 1xxxx
|
||
#
|
||
'-10001': OperationFailed, # The system is under maintenance, please try again later.
|
||
'-10002': BadRequest, # Invalid input parameters.
|
||
'-10005': BadResponse, # No records found.
|
||
'-10007': BadRequest, # This coin is not loanable
|
||
'-10008': BadRequest, # This coin is not loanable
|
||
'-10009': BadRequest, # This coin can not be used.
|
||
'-10010': BadRequest, # This coin can not be used.
|
||
'-10011': InsufficientFunds, # Insufficient spot assets.
|
||
'-10012': BadRequest, # Invalid repayment amount.
|
||
'-10013': InsufficientFunds, # Insufficient collateral amount.
|
||
'-10015': OperationFailed, # Collateral deduction failed.
|
||
'-10016': OperationFailed, # Failed to provide loan.
|
||
'-10017': OperationRejected, # {"code":-10017,"msg":"Repay amount should not be larger than liability."}
|
||
'-10018': BadRequest, # Invalid repayment amount.
|
||
'-10019': BadRequest, # Configuration does not exists.
|
||
'-10020': BadRequest, # User ID does not exist.
|
||
'-10021': InvalidOrder, # Order does not exist.
|
||
'-10022': BadRequest, # Invalid adjustment amount.
|
||
'-10023': OperationFailed, # Failed to adjust LTV.
|
||
'-10024': BadRequest, # LTV adjustment not supported.
|
||
'-10025': OperationFailed, # Repayment failed.
|
||
'-10026': BadRequest, # Invalid parameter.
|
||
'-10028': BadRequest, # Invalid parameter.
|
||
'-10029': OperationRejected, # Loan amount is too small.
|
||
'-10030': OperationRejected, # Loan amount is too much.
|
||
'-10031': OperationRejected, # Individual loan quota reached.
|
||
'-10032': OperationFailed, # Repayment is temporarily unavailable.
|
||
'-10034': OperationRejected, # Repay with collateral is not available currently, please try to repay with borrowed coin.
|
||
'-10039': OperationRejected, # Repayment amount is too small.
|
||
'-10040': OperationRejected, # Repayment amount is too large.
|
||
'-10041': OperationFailed, # Due to high demand, there are currently insufficient loanable assets for {0}. Please adjust your borrow amount or try again tomorrow.
|
||
'-10042': BadSymbol, # asset %s is not supported
|
||
'-10043': OperationRejected, # {0} borrowing is currently not supported.
|
||
'-10044': OperationRejected, # Collateral amount has reached the limit. Please reduce your collateral amount or try with other collaterals.
|
||
'-10045': OperationRejected, # The loan coin does not support collateral repayment. Please try again later.
|
||
'-10046': OperationRejected, # Collateral Adjustment exceeds the maximum limit. Please try again.
|
||
'-10047': PermissionDenied, # This coin is currently not supported in your location due to local regulations.
|
||
'-11008': OperationRejected, # undocumented: Exceeding the account’s maximum borrowable limit
|
||
'-12014': RateLimitExceeded, # More than 1 request in 2 seconds
|
||
# BLVT
|
||
'-13000': OperationRejected, # Redeption of the token is forbiden now
|
||
'-13001': OperationRejected, # Exceeds individual 24h redemption limit of the token
|
||
'-13002': OperationRejected, # Exceeds total 24h redemption limit of the token
|
||
'-13003': PermissionDenied, # Subscription of the token is forbiden now
|
||
'-13004': OperationRejected, # Exceeds individual 24h subscription limit of the token
|
||
'-13005': OperationRejected, # Exceeds total 24h subscription limit of the token
|
||
'-13006': OperationRejected, # Subscription amount is too small
|
||
'-13007': PermissionDenied, # The Agreement is not signed
|
||
# 18xxx - BINANCE CODE
|
||
'-18002': OperationRejected, # The total amount of codes you created has exceeded the 24-hour limit, please try again after UTC 0
|
||
'-18003': OperationRejected, # Too many codes created in 24 hours, please try again after UTC 0
|
||
'-18004': OperationRejected, # Too many invalid redeem attempts in 24 hours, please try again after UTC 0
|
||
'-18005': PermissionDenied, # Too many invalid verify attempts, please try later
|
||
'-18006': OperationRejected, # The amount is too small, please re-enter
|
||
'-18007': OperationRejected, # This token is not currently supported, please re-enter
|
||
#
|
||
# 2xxxx
|
||
#
|
||
# 21xxx - PORTFOLIO MARGIN(documented in spot docs)
|
||
'-21001': BadRequest, # Request ID is not a Portfolio Margin Account.
|
||
'-21002': BadRequest, # Portfolio Margin Account doesn't support transfer from margin to futures.
|
||
'-21003': BadResponse, # Fail to retrieve margin assets.
|
||
'-21004': OperationRejected, # User doesn’t have portfolio margin bankruptcy loan
|
||
'-21005': InsufficientFunds, # User’s spot wallet doesn’t have enough BUSD to repay portfolio margin bankruptcy loan
|
||
'-21006': OperationFailed, # User had portfolio margin bankruptcy loan repayment in process
|
||
'-21007': OperationFailed, # User failed to repay portfolio margin bankruptcy loan since liquidation was in process
|
||
#
|
||
# misc
|
||
#
|
||
'-32603': BadRequest, # undocumented, Filter failure: LOT_SIZE & precision
|
||
'400002': BadRequest, # undocumented, {“status”: “FAIL”, “code”: “400002”, “errorMessage”: “Signature for self request is not valid.”}
|
||
'100001003': AuthenticationError, # undocumented, {"code":100001003,"msg":"Verification failed"}
|
||
'200003903': AuthenticationError, # undocumented, {"code":200003903,"msg":"Your identity verification has been rejected. Please complete identity verification again."}
|
||
},
|
||
},
|
||
'linear': {
|
||
'exact': {
|
||
#
|
||
# 1xxx
|
||
#
|
||
'-1005': PermissionDenied, # {"code":-1005,"msg":"No such IP has been white listed"}
|
||
'-1008': OperationFailed, # -1008 SERVER_BUSY: Server is currently overloaded with other requests. Please try again in a few minutes.
|
||
'-1011': PermissionDenied, # {"code":-1011,"msg":"This IP cannot access self route."}
|
||
'-1023': BadRequest, # {"code":-1023,"msg":"Start time is greater than end time."}
|
||
'-1099': AuthenticationError, # {"code":-1099,"msg":"Not found, authenticated, or authorized"}
|
||
'-1109': PermissionDenied, # {"code":-1109,"msg":"Invalid account."}
|
||
'-1110': BadRequest, # {"code":-1110,"msg":"Invalid symbolType."}
|
||
'-1113': BadRequest, # {"code":-1113,"msg":"Withdrawal amount must be negative."}
|
||
'-1122': BadRequest, # INVALID_SYMBOL_STATUS
|
||
'-1126': BadSymbol, # ASSET_NOT_SUPPORTED
|
||
'-1136': BadRequest, # {"code":-1136,"msg":"Invalid newOrderRespType"}
|
||
#
|
||
# 2xxx
|
||
#
|
||
'-2012': OperationFailed, # CANCEL_ALL_FAIL
|
||
'-2016': OperationRejected, # {"code":-2016,"msg":"No trading window could be found for the symbol. Try ticker/24hrs instead."}
|
||
'-2017': PermissionDenied, # API Keys are locked on self account.
|
||
'-2018': InsufficientFunds, # {"code":-2018,"msg":"Balance is insufficient"}
|
||
'-2019': InsufficientFunds, # {"code":-2019,"msg":"Margin is insufficient."}
|
||
'-2020': OperationFailed, # {"code":-2020,"msg":"Unable to fill."}
|
||
'-2021': OrderImmediatelyFillable, # {"code":-2021,"msg":"Order would immediately trigger."}
|
||
'-2022': InvalidOrder, # {"code":-2022,"msg":"ReduceOnly Order is rejected."}
|
||
'-2023': OperationFailed, # {"code":-2023,"msg":"User in liquidation mode now."}
|
||
'-2024': InsufficientFunds, # {"code":-2024,"msg":"Position is not sufficient."}
|
||
'-2025': OperationRejected, # {"code":-2025,"msg":"Reach max open order limit."}
|
||
'-2026': InvalidOrder, # {"code":-2026,"msg":"This OrderType is not supported when reduceOnly."}
|
||
'-2027': OperationRejected, # {"code":-2027,"msg":"Exceeded the maximum allowable position at current leverage."}
|
||
'-2028': OperationRejected, # {"code":-2028,"msg":"Leverage is smaller than permitted: insufficient margin balance"}
|
||
#
|
||
# 4xxx
|
||
#
|
||
'-4063': BadRequest, # INVALID_OPTIONS_REQUEST_TYPE
|
||
'-4064': BadRequest, # INVALID_OPTIONS_TIME_FRAME
|
||
'-4065': BadRequest, # INVALID_OPTIONS_AMOUNT
|
||
'-4066': BadRequest, # INVALID_OPTIONS_EVENT_TYPE
|
||
'-4069': BadRequest, # Position INVALID_OPTIONS_PREMIUM_FEE
|
||
'-4070': BadRequest, # Client options id is not valid.
|
||
'-4071': BadRequest, # Invalid options direction
|
||
'-4072': OperationRejected, # premium fee is not updated, reject order
|
||
'-4073': BadRequest, # OPTIONS_PREMIUM_INPUT_LESS_THAN_ZERO
|
||
'-4074': OperationRejected, # Order amount is bigger than upper boundary or less than 0, reject order
|
||
'-4075': BadRequest, # output premium fee is less than 0, reject order
|
||
'-4076': OperationRejected, # original fee is too much higher than last fee
|
||
'-4077': OperationRejected, # place order amount has reached to limit, reject order
|
||
'-4078': OperationFailed, # options internal error
|
||
'-4079': BadRequest, # invalid options id
|
||
'-4080': PermissionDenied, # user not found with id: %s
|
||
'-4081': BadRequest, # OPTIONS_NOT_FOUND
|
||
'-4085': BadRequest, # Invalid notional limit coefficient
|
||
'-4087': PermissionDenied, # User can only place reduce only order
|
||
'-4088': PermissionDenied, # User can not place order currently
|
||
'-4114': BadRequest, # INVALID_CLIENT_TRAN_ID_LEN
|
||
'-4115': BadRequest, # DUPLICATED_CLIENT_TRAN_ID
|
||
'-4116': InvalidOrder, # DUPLICATED_CLIENT_ORDER_ID
|
||
'-4117': OperationRejected, # STOP_ORDER_TRIGGERING
|
||
'-4118': OperationRejected, # REDUCE_ONLY_MARGIN_CHECK_FAILED
|
||
'-4131': OperationRejected, # The counterparty's best price does not meet the PERCENT_PRICE filter limit
|
||
'-4140': BadRequest, # Invalid symbol status for opening position
|
||
'-4141': OperationRejected, # Symbol is closed
|
||
'-4144': BadSymbol, # Invalid pair
|
||
'-4164': InvalidOrder, # {"code":-4164,"msg":"Order's notional must be no smaller than 20(unless you choose reduce only)."},
|
||
'-4136': InvalidOrder, # {"code":-4136,"msg":"Target strategy invalid for orderType TRAILING_STOP_MARKET,closePosition True"}
|
||
'-4165': BadRequest, # Invalid time interval
|
||
'-4167': BadRequest, # Unable to adjust to Multi-Assets mode with symbols of USDⓈ-M Futures under isolated-margin mode.
|
||
'-4168': BadRequest, # Unable to adjust to isolated-margin mode under the Multi-Assets mode.
|
||
'-4169': OperationRejected, # Unable to adjust Multi-Assets Mode with insufficient margin balance in USDⓈ-M Futures
|
||
'-4170': OperationRejected, # Unable to adjust Multi-Assets Mode with open orders in USDⓈ-M Futures
|
||
'-4171': OperationRejected, # Adjusted asset mode is currently set and does not need to be adjusted repeatedly
|
||
'-4172': OperationRejected, # Unable to adjust Multi-Assets Mode with a negative wallet balance of margin available asset in USDⓈ-M Futures account.
|
||
'-4183': BadRequest, # Price is higher than stop price multiplier cap.
|
||
'-4184': BadRequest, # Price is lower than stop price multiplier floor.
|
||
'-4192': PermissionDenied, # Trade forbidden due to Cooling-off Period.
|
||
'-4202': PermissionDenied, # Intermediate Personal Verification is required for adjusting leverage over 20x
|
||
'-4203': PermissionDenied, # More than 20x leverage is available one month after account registration.
|
||
'-4205': PermissionDenied, # More than 20x leverage is available %s days after Futures account registration.
|
||
'-4206': PermissionDenied, # hasattr(self, Users) country has limited adjust leverage.
|
||
'-4208': OperationRejected, # Current symbol leverage cannot exceed 20 when using position limit adjustment service.
|
||
'-4209': OperationRejected, # Leverage adjustment failed. Current symbol max leverage limit is %sx
|
||
'-4210': BadRequest, # Stop price is higher than price multiplier cap
|
||
'-4211': BadRequest, # Stop price is lower than price multiplier floor
|
||
'-4400': PermissionDenied, # Futures Trading Quantitative Rules violated, only reduceOnly order is allowed, please try again later.
|
||
'-4401': PermissionDenied, # Compliance restricted account permission: can only place reduceOnly order.
|
||
'-4402': PermissionDenied, # Dear user, our Terms of Use and compliance with local regulations, self feature is currently not available in your region.
|
||
'-4403': PermissionDenied, # Dear user, our Terms of Use and compliance with local regulations, the leverage can only up to %sx in your region
|
||
#
|
||
# 5xxx
|
||
#
|
||
'-5021': OrderNotFillable, # Due to the order could not be filled immediately, the FOK order has been rejected.
|
||
'-5022': OrderNotFillable, # Due to the order could not be executed, the Post Only order will be rejected.
|
||
'-5024': OperationRejected, # Symbol is not in trading status. Order amendment is not permitted.
|
||
'-5025': OperationRejected, # Only limit order is supported.
|
||
'-5026': OperationRejected, # Exceed maximum modify order limit.
|
||
'-5027': OperationRejected, # No need to modify the order.
|
||
'-5028': BadRequest, # Timestamp for self request is outside of the ME recvWindow.
|
||
'-5037': BadRequest, # Invalid price match
|
||
'-5038': BadRequest, # Price match only supports order type: LIMIT, STOP AND TAKE_PROFIT
|
||
'-5039': BadRequest, # Invalid self trade prevention mode
|
||
'-5040': BadRequest, # The goodTillDate timestamp must be greater than the current time plus 600 seconds and smaller than 253402300799000
|
||
'-5041': OperationFailed, # No depth matches self BBO order
|
||
},
|
||
},
|
||
'inverse': {
|
||
'exact': {
|
||
#
|
||
# 1xxx
|
||
#
|
||
'-1005': PermissionDenied, # {"code":-1005,"msg":"No such IP has been white listed"}
|
||
'-1011': PermissionDenied, # {"code":-1011,"msg":"This IP cannot access self route."}
|
||
'-1023': BadRequest, # {"code":-1023,"msg":"Start time is greater than end time."}
|
||
'-1109': AuthenticationError, # {"code":-1109,"msg":"Invalid account."}
|
||
'-1110': BadSymbol, # {"code":-1110,"msg":"Invalid symbolType."}
|
||
'-1113': BadRequest, # {"code":-1113,"msg":"Withdrawal amount must be negative."}
|
||
'-1128': BadRequest, # {"code":-1128,"msg":"Combination of optional parameters invalid."}
|
||
'-1136': BadRequest, # {"code":-1136,"msg":"Invalid newOrderRespType"}
|
||
#
|
||
# 2xxx
|
||
#
|
||
'-2016': OperationRejected, # {"code":-2016,"msg":"No trading window could be found for the symbol. Try ticker/24hrs instead."}
|
||
'-2018': InsufficientFunds, # {"code":-2018,"msg":"Balance is insufficient"}
|
||
'-2019': InsufficientFunds, # {"code":-2019,"msg":"Margin is insufficient."}
|
||
'-2020': OperationFailed, # {"code":-2020,"msg":"Unable to fill."}
|
||
'-2021': OrderImmediatelyFillable, # {"code":-2021,"msg":"Order would immediately trigger."}
|
||
'-2022': InvalidOrder, # {"code":-2022,"msg":"ReduceOnly Order is rejected."}
|
||
'-2023': OperationFailed, # {"code":-2023,"msg":"User in liquidation mode now."}
|
||
'-2024': BadRequest, # {"code":-2024,"msg":"Position is not sufficient."}
|
||
'-2025': OperationRejected, # {"code":-2025,"msg":"Reach max open order limit."}
|
||
'-2026': InvalidOrder, # {"code":-2026,"msg":"This OrderType is not supported when reduceOnly."}
|
||
'-2027': OperationRejected, # {"code":-2027,"msg":"Exceeded the maximum allowable position at current leverage."}
|
||
'-2028': OperationRejected, # {"code":-2028,"msg":"Leverage is smaller than permitted: insufficient margin balance"}
|
||
#
|
||
# 4xxx
|
||
#
|
||
'-4086': BadRequest, # Invalid price spread threshold.
|
||
'-4087': BadSymbol, # Invalid pair
|
||
'-4088': BadRequest, # Invalid time interval
|
||
'-4089': PermissionDenied, # User can only place reduce only order.
|
||
'-4090': PermissionDenied, # User can not place order currently.
|
||
'-4110': BadRequest, # clientTranId is not valid
|
||
'-4111': BadRequest, # clientTranId is duplicated.
|
||
'-4112': OperationRejected, # ReduceOnly Order Failed. Please check your existing position and open orders.
|
||
'-4113': OperationRejected, # The counterparty's best price does not meet the PERCENT_PRICE filter limit.
|
||
'-4150': OperationRejected, # Leverage reduction is not supported in Isolated Margin Mode with open positions.
|
||
'-4151': BadRequest, # Price is higher than stop price multiplier cap.
|
||
'-4152': BadRequest, # Price is lower than stop price multiplier floor.
|
||
'-4154': BadRequest, # Stop price is higher than price multiplier cap.
|
||
'-4155': BadRequest, # Stop price is lower than price multiplier floor
|
||
'-4178': BadRequest, # Order's notional must be no smaller than one(unless you choose reduce only)
|
||
'-4188': BadRequest, # Timestamp for self request is outside of the ME recvWindow.
|
||
'-4192': PermissionDenied, # Trade forbidden due to Cooling-off Period.
|
||
'-4194': PermissionDenied, # Intermediate Personal Verification is required for adjusting leverage over 20x.
|
||
'-4195': PermissionDenied, # More than 20x leverage is available one month after account registration.
|
||
'-4196': BadRequest, # Only limit order is supported.
|
||
'-4197': OperationRejected, # No need to modify the order.
|
||
'-4198': OperationRejected, # Exceed maximum modify order limit.
|
||
'-4199': BadRequest, # Symbol is not in trading status. Order amendment is not permitted.
|
||
'-4200': PermissionDenied, # More than 20x leverage is available %s days after Futures account registration.
|
||
'-4201': PermissionDenied, # Users in your location/country can only access a maximum leverage of %s
|
||
'-4202': OperationRejected, # Current symbol leverage cannot exceed 20 when using position limit adjustment service.
|
||
},
|
||
},
|
||
'option': {
|
||
'exact': {
|
||
#
|
||
# 1xxx
|
||
#
|
||
'-1003': ExchangeError, # override common
|
||
'-1004': ExchangeError, # override common
|
||
'-1006': ExchangeError, # override common
|
||
'-1007': ExchangeError, # override common
|
||
'-1008': RateLimitExceeded, # TOO_MANY_REQUEST
|
||
'-1010': ExchangeError, # override common
|
||
'-1013': ExchangeError, # override common
|
||
'-1108': ExchangeError, # override common
|
||
'-1112': ExchangeError, # override common
|
||
'-1114': ExchangeError, # override common
|
||
'-1128': BadSymbol, # BAD_CONTRACT
|
||
'-1129': BadSymbol, # BAD_CURRENCY
|
||
'-1131': BadRequest, # {"code":-1131,"msg":"recvWindow must be less than 60000"}
|
||
#
|
||
# 2xxx
|
||
#
|
||
'-2011': ExchangeError, # override common
|
||
'-2018': InsufficientFunds, # BALANCE_NOT_SUFFICIENT
|
||
'-2027': InsufficientFunds, # OPTION_MARGIN_NOT_SUFFICIENT
|
||
#
|
||
# 3xxx
|
||
#
|
||
'-3029': OperationFailed, # {"code":-3029,"msg":"Transfer failed."}
|
||
#
|
||
# 4xxx
|
||
#
|
||
# -4001 inherited
|
||
# -4002 inherited
|
||
# -4003 inherited
|
||
# -4004 inherited
|
||
# -4005 inherited
|
||
'-4006': ExchangeError, # override commons
|
||
'-4007': ExchangeError, # override commons
|
||
'-4008': ExchangeError, # override commons
|
||
'-4009': ExchangeError, # override commons
|
||
'-4010': ExchangeError, # override commons
|
||
'-4011': ExchangeError, # override commons
|
||
'-4012': ExchangeError, # override commons
|
||
# -4013 inherited
|
||
'-4014': ExchangeError, # override commons
|
||
'-4015': ExchangeError, # override commons
|
||
'-4016': ExchangeError, # override commons
|
||
'-4017': ExchangeError, # override commons
|
||
'-4018': ExchangeError, # override commons
|
||
'-4019': ExchangeError, # override commons
|
||
'-4020': ExchangeError, # override commons
|
||
'-4021': ExchangeError, # override commons
|
||
'-4022': ExchangeError, # override commons
|
||
'-4023': ExchangeError, # override commons
|
||
'-4024': ExchangeError, # override commons
|
||
'-4025': ExchangeError, # override commons
|
||
'-4026': ExchangeError, # override commons
|
||
'-4027': ExchangeError, # override commons
|
||
'-4028': ExchangeError, # override commons
|
||
# -4029 inherited
|
||
# -4030 inherited
|
||
'-4031': ExchangeError, # override commons
|
||
'-4032': ExchangeError, # override commons
|
||
'-4033': ExchangeError, # override commons
|
||
'-4034': ExchangeError, # override commons
|
||
'-4035': ExchangeError, # override commons
|
||
'-4036': ExchangeError, # override commons
|
||
'-4037': ExchangeError, # override commons
|
||
'-4038': ExchangeError, # override commons
|
||
'-4039': ExchangeError, # override commons
|
||
'-4040': ExchangeError, # override commons
|
||
'-4041': ExchangeError, # override commons
|
||
'-4042': ExchangeError, # override commons
|
||
'-4043': ExchangeError, # override commons
|
||
'-4044': ExchangeError, # override commons
|
||
'-4045': ExchangeError, # override commons
|
||
'-4046': ExchangeError, # override commons
|
||
'-4047': ExchangeError, # override commons
|
||
'-4048': ExchangeError, # override commons
|
||
'-4049': ExchangeError, # override commons
|
||
'-4050': ExchangeError, # override commons
|
||
'-4051': ExchangeError, # override commons
|
||
'-4052': ExchangeError, # override commons
|
||
'-4053': ExchangeError, # override commons
|
||
'-4054': ExchangeError, # override commons
|
||
# -4055 inherited
|
||
'-4056': ExchangeError, # override commons
|
||
'-4057': ExchangeError, # override commons
|
||
'-4058': ExchangeError, # override commons
|
||
'-4059': ExchangeError, # override commons
|
||
'-4060': ExchangeError, # override commons
|
||
'-4061': ExchangeError, # override commons
|
||
'-4062': ExchangeError, # override commons
|
||
'-4063': ExchangeError, # override commons
|
||
'-4064': ExchangeError, # override commons
|
||
'-4065': ExchangeError, # override commons
|
||
'-4066': ExchangeError, # override commons
|
||
'-4067': ExchangeError, # override commons
|
||
'-4068': ExchangeError, # override commons
|
||
'-4069': ExchangeError, # override commons
|
||
'-4070': ExchangeError, # override commons
|
||
'-4071': ExchangeError, # override commons
|
||
'-4072': ExchangeError, # override commons
|
||
'-4073': ExchangeError, # override commons
|
||
'-4074': ExchangeError, # override commons
|
||
'-4075': ExchangeError, # override commons
|
||
'-4076': ExchangeError, # override commons
|
||
'-4077': ExchangeError, # override commons
|
||
'-4078': ExchangeError, # override commons
|
||
'-4079': ExchangeError, # override commons
|
||
'-4080': ExchangeError, # override commons
|
||
'-4081': ExchangeError, # override commons
|
||
'-4082': ExchangeError, # override commons
|
||
'-4083': ExchangeError, # override commons
|
||
'-4084': ExchangeError, # override commons
|
||
'-4085': ExchangeError, # override commons
|
||
'-4086': ExchangeError, # override commons
|
||
'-4087': ExchangeError, # override commons
|
||
'-4088': ExchangeError, # override commons
|
||
'-4089': ExchangeError, # override commons
|
||
'-4091': ExchangeError, # override commons
|
||
'-4092': ExchangeError, # override commons
|
||
'-4093': ExchangeError, # override commons
|
||
'-4094': ExchangeError, # override commons
|
||
'-4095': ExchangeError, # override commons
|
||
'-4096': ExchangeError, # override commons
|
||
'-4097': ExchangeError, # override commons
|
||
'-4098': ExchangeError, # override commons
|
||
'-4099': ExchangeError, # override commons
|
||
'-4101': ExchangeError, # override commons
|
||
'-4102': ExchangeError, # override commons
|
||
'-4103': ExchangeError, # override commons
|
||
'-4104': ExchangeError, # override commons
|
||
'-4105': ExchangeError, # override commons
|
||
'-4106': ExchangeError, # override commons
|
||
'-4107': ExchangeError, # override commons
|
||
'-4108': ExchangeError, # override commons
|
||
'-4109': ExchangeError, # override commons
|
||
'-4110': ExchangeError, # override commons
|
||
'-4112': ExchangeError, # override commons
|
||
'-4113': ExchangeError, # override commons
|
||
'-4114': ExchangeError, # override commons
|
||
'-4115': ExchangeError, # override commons
|
||
'-4116': ExchangeError, # override commons
|
||
'-4117': ExchangeError, # override commons
|
||
'-4118': ExchangeError, # override commons
|
||
'-4119': ExchangeError, # override commons
|
||
'-4120': ExchangeError, # override commons
|
||
'-4121': ExchangeError, # override commons
|
||
'-4122': ExchangeError, # override commons
|
||
'-4123': ExchangeError, # override commons
|
||
'-4124': ExchangeError, # override commons
|
||
'-4125': ExchangeError, # override commons
|
||
'-4126': ExchangeError, # override commons
|
||
'-4127': ExchangeError, # override commons
|
||
'-4128': ExchangeError, # override commons
|
||
'-4129': ExchangeError, # override commons
|
||
'-4130': ExchangeError, # override commons
|
||
'-4131': ExchangeError, # override commons
|
||
'-4132': ExchangeError, # override commons
|
||
'-4133': ExchangeError, # override commons
|
||
'-4134': ExchangeError, # override commons
|
||
'-4135': ExchangeError, # override commons
|
||
'-4136': ExchangeError, # override commons
|
||
'-4137': ExchangeError, # override commons
|
||
'-4138': ExchangeError, # override commons
|
||
'-4139': ExchangeError, # override commons
|
||
'-4141': ExchangeError, # override commons
|
||
'-4142': ExchangeError, # override commons
|
||
'-4143': ExchangeError, # override commons
|
||
'-4144': ExchangeError, # override commons
|
||
'-4145': ExchangeError, # override commons
|
||
'-4146': ExchangeError, # override commons
|
||
'-4147': ExchangeError, # override commons
|
||
'-4148': ExchangeError, # override commons
|
||
'-4149': ExchangeError, # override commons
|
||
'-4150': ExchangeError, # override commons
|
||
#
|
||
# 2xxxx
|
||
#
|
||
'-20121': ExchangeError, # override commons
|
||
'-20124': ExchangeError, # override commons
|
||
'-20130': ExchangeError, # override commons
|
||
'-20132': ExchangeError, # override commons
|
||
'-20194': ExchangeError, # override commons
|
||
'-20195': ExchangeError, # override commons
|
||
'-20196': ExchangeError, # override commons
|
||
'-20198': ExchangeError, # override commons
|
||
'-20204': ExchangeError, # override commons
|
||
},
|
||
},
|
||
'portfolioMargin': {
|
||
'exact': {
|
||
#
|
||
# 10xx General Server or Network Issues
|
||
#
|
||
'-1000': OperationFailed, # An unknown error occured while processing the request.
|
||
'-1001': ExchangeError, # Internal error; unable to process your request. Please try again.
|
||
'-1002': PermissionDenied, # You are not authorized to execute self request.
|
||
'-1003': RateLimitExceeded, # Too many requests use the websocket for live updates to avoid polling the API.
|
||
'-1004': BadRequest, # This IP is already on the white list.
|
||
'-1005': PermissionDenied, # No such IP has been white listed.
|
||
'-1006': BadResponse, # An unexpected response was received from the message bus. Execution status unknown.
|
||
'-1007': BadResponse, # Timeout waiting for response from backend server. Send status unknown, execution status unknown.
|
||
'-1008': OperationFailed, # WS Spot server is currently overloaded with other requests. Please try again in a few minutes.
|
||
'-1010': ExchangeError, # ERROR_MSG_RECEIVED
|
||
'-1011': PermissionDenied, # This IP cannot access self route.
|
||
'-1013': ExchangeError, # INVALID_MESSAGE.
|
||
'-1014': InvalidOrder, # Unsupported order combination.
|
||
'-1015': InvalidOrder, # Too many new orders.
|
||
'-1016': NotSupported, # This service is no longer available.
|
||
'-1020': NotSupported, # This operation is not supported.
|
||
'-1021': BadRequest, # Timestamp for self request is outside of the recvWindow 1000ms ahead of the servers time.
|
||
'-1022': BadRequest, # Signature for self request is not valid.
|
||
'-1023': BadRequest, # Start time is greater than end time
|
||
'-1099': OperationFailed, # WS not found authenticated or authorized
|
||
#
|
||
# 11xx Request Issues
|
||
#
|
||
'-1100': BadRequest, # Illegal characters found in a parameter.
|
||
'-1101': BadRequest, # Too many parameters sent for self endpoint.
|
||
'-1102': BadRequest, # A mandatory parameter was not sent, was empty/null, or malformed.
|
||
'-1103': BadRequest, # An unknown parameter was sent.
|
||
'-1104': BadRequest, # Not all sent parameters were read.
|
||
'-1105': BadRequest, # A parameter was empty.
|
||
'-1106': BadRequest, # A parameter was sent when not required.
|
||
'-1108': BadRequest, # Invalid asset.
|
||
'-1109': BadRequest, # Invalid account.
|
||
'-1110': BadSymbol, # Invalid symbolType.
|
||
'-1111': BadRequest, # Precision is over the maximum defined for self asset.
|
||
'-1112': BadRequest, # No orders on book for symbol.
|
||
'-1113': BadRequest, # Withdrawal amount must be negative.
|
||
'-1114': BadRequest, # TimeInForce parameter sent when not required.
|
||
'-1115': BadRequest, # Invalid timeInForce.
|
||
'-1116': BadRequest, # Invalid orderType.
|
||
'-1117': BadRequest, # Invalid side.
|
||
'-1118': BadRequest, # New client order ID was empty.
|
||
'-1119': BadRequest, # Original client order ID was empty.
|
||
'-1120': BadRequest, # Invalid interval.
|
||
'-1121': BadSymbol, # Invalid symbol.
|
||
'-1125': BadRequest, # This listenKey does not exist.
|
||
'-1127': BadRequest, # Lookup interval is too big.
|
||
'-1128': BadRequest, # Combination of optional parameters invalid.
|
||
'-1130': BadRequest, # Invalid data sent for a parameter.
|
||
'-1131': BadRequest, # WS recvWindow must be less than 60000
|
||
'-1134': BadRequest, # WS strategyType was less than 1000000.
|
||
'-1136': BadRequest, # Invalid newOrderRespType.
|
||
'-1145': BadRequest, # WS cancelRestrictions has to be either ONLY_NEW or ONLY_PARTIALLY_FILLED.
|
||
'-1151': BadRequest, # WS Symbol is present multiple times in the list.
|
||
#
|
||
# 20xx Processing Issues
|
||
#
|
||
'-2010': InvalidOrder, # NEW_ORDER_REJECTED
|
||
'-2011': OperationRejected, # CANCEL_REJECTED
|
||
'-2013': OrderNotFound, # Order does not exist.
|
||
'-2014': OperationRejected, # API-key format invalid.
|
||
'-2015': OperationRejected, # Invalid API-key, IP, or permissions for action.
|
||
'-2016': OperationFailed, # No trading window could be found for the symbol. Try ticker/24hrs instead.
|
||
'-2018': OperationFailed, # Balance is insufficient.
|
||
'-2019': OperationFailed, # Margin is insufficient.
|
||
'-2020': OrderNotFillable, # Unable to fill.
|
||
'-2021': OrderImmediatelyFillable, # Order would immediately trigger.
|
||
'-2022': InvalidOrder, # ReduceOnly Order is rejected.
|
||
'-2023': OperationFailed, # User in liquidation mode now.
|
||
'-2024': OperationRejected, # Position is not sufficient.
|
||
'-2025': OperationRejected, # Reach max open order limit.
|
||
'-2026': InvalidOrder, # This OrderType is not supported when reduceOnly.
|
||
'-2027': OperationRejected, # Exceeded the maximum allowable position at current leverage.
|
||
'-2028': OperationRejected, # Leverage is smaller than permitted: insufficient margin balance.
|
||
#
|
||
# 4xxx Filters and other issues
|
||
#
|
||
'-4000': BadRequest, # Invalid order status.
|
||
'-4001': BadRequest, # Price less than 0.
|
||
'-4002': BadRequest, # Price greater than max price.
|
||
'-4003': BadRequest, # Quantity less than zero.
|
||
'-4004': BadRequest, # Quantity less than min quantity.
|
||
'-4005': BadRequest, # Quantity greater than max quantity.
|
||
'-4006': BadRequest, # Stop price less than zero.
|
||
'-4007': BadRequest, # Stop price greater than max price.
|
||
'-4008': BadRequest, # Tick size less than zero.
|
||
'-4009': BadRequest, # Max price less than min price.
|
||
'-4010': BadRequest, # Max qty less than min qty.
|
||
'-4011': BadRequest, # Step size less than zero.
|
||
'-4012': BadRequest, # Max mum orders less than zero.
|
||
'-4013': BadRequest, # Price less than min price.
|
||
'-4014': BadRequest, # Price not increased by tick size.
|
||
'-4015': BadRequest, # Client order id is not valid.
|
||
'-4016': BadRequest, # Price is higher than mark price multiplier cap.
|
||
'-4017': BadRequest, # Multiplier up less than zero.
|
||
'-4018': BadRequest, # Multiplier down less than zero.
|
||
'-4019': BadRequest, # Composite scale too large.
|
||
'-4020': BadRequest, # Target strategy invalid for orderType '%s',reduceOnly '%b'.
|
||
'-4021': BadRequest, # Invalid depth limit.
|
||
'-4022': BadRequest, # market status sent is not valid.
|
||
'-4023': BadRequest, # Qty not increased by step size.
|
||
'-4024': BadRequest, # Price is lower than mark price multiplier floor.
|
||
'-4025': BadRequest, # Multiplier decimal less than zero.
|
||
'-4026': BadRequest, # Commission invalid.
|
||
'-4027': BadRequest, # Invalid account type.
|
||
'-4028': BadRequest, # Invalid leverage
|
||
'-4029': BadRequest, # Tick size precision is invalid.
|
||
'-4030': BadRequest, # Step size precision is invalid.
|
||
'-4031': BadRequest, # Invalid parameter working type
|
||
'-4032': BadRequest, # Exceed maximum cancel order size.
|
||
'-4033': BadRequest, # Insurance account not found.
|
||
'-4044': BadRequest, # Balance Type is invalid.
|
||
'-4045': BadRequest, # Reach max stop order limit.
|
||
'-4046': BadRequest, # No need to change margin type.
|
||
'-4047': BadRequest, # Margin type cannot be changed if there exists open orders.
|
||
'-4048': BadRequest, # Margin type cannot be changed if there exists position.
|
||
'-4049': BadRequest, # Add margin only support for isolated position.
|
||
'-4050': BadRequest, # Cross balance insufficient.
|
||
'-4051': BadRequest, # Isolated balance insufficient.
|
||
'-4052': BadRequest, # No need to change auto add margin.
|
||
'-4053': BadRequest, # Auto add margin only support for isolated position.
|
||
'-4054': BadRequest, # Cannot add position margin: position is 0.
|
||
'-4055': BadRequest, # Amount must be positive.
|
||
'-4056': PermissionDenied, # Invalid api key type.
|
||
'-4057': PermissionDenied, # Invalid api public key
|
||
'-4058': BadRequest, # maxPrice and priceDecimal too large,please check.
|
||
'-4059': BadRequest, # No need to change position side.
|
||
'-4060': BadRequest, # Invalid position side.
|
||
'-4061': InvalidOrder, # Order's position side does not match user's setting.
|
||
'-4062': BadRequest, # Invalid or improper reduceOnly value.
|
||
'-4063': BadRequest, # Invalid options request type
|
||
'-4064': BadRequest, # Invalid options time frame
|
||
'-4065': BadRequest, # Invalid options amount
|
||
'-4066': BadRequest, # Invalid options event type
|
||
'-4067': BadRequest, # Position side cannot be changed if there exists open orders.
|
||
'-4068': BadRequest, # Position side cannot be changed if there exists position.
|
||
'-4069': BadRequest, # Invalid options premium fee
|
||
'-4070': BadRequest, # Client options id is not valid.
|
||
'-4071': BadRequest, # Invalid options direction
|
||
'-4072': OperationRejected, # premium fee is not updated, reject order
|
||
'-4073': BadRequest, # input premium fee is less than 0, reject order
|
||
'-4074': BadRequest, # Order amount is bigger than upper boundary or less than 0, reject order
|
||
'-4075': BadRequest, # output premium fee is less than 0, reject order
|
||
'-4076': OperationRejected, # original fee is too much higher than last fee
|
||
'-4077': OperationRejected, # place order amount has reached to limit, reject order
|
||
'-4078': OperationFailed, # options internal error
|
||
'-4079': BadRequest, # invalid options id
|
||
'-4080': PermissionDenied, # user not found
|
||
'-4081': BadRequest, # options not found
|
||
'-4082': BadRequest, # Invalid number of batch place orders.
|
||
'-4083': BadRequest, # Fail to place batch orders.
|
||
'-4084': NotSupported, # Method is not allowed currently. Upcoming soon.
|
||
'-4085': BadRequest, # Invalid notional limit coefficient
|
||
'-4086': BadRequest, # Invalid price spread threshold
|
||
'-4087': PermissionDenied, # User can only place reduce only order
|
||
'-4088': PermissionDenied, # User can not place order currently
|
||
'-4104': BadRequest, # Invalid contract type
|
||
'-4114': BadRequest, # clientTranId is not valid
|
||
'-4115': BadRequest, # clientTranId is duplicated
|
||
'-4118': OperationRejected, # ReduceOnly Order Failed. Please check your existing position and open orders
|
||
'-4131': OperationRejected, # The counterparty's best price does not meet the PERCENT_PRICE filter limit
|
||
'-4135': BadRequest, # Invalid activation price
|
||
'-4137': BadRequest, # Quantity must be zero with closePosition equals True
|
||
'-4138': BadRequest, # Reduce only must be True with closePosition equals True
|
||
'-4139': BadRequest, # Order type can not be market if it's unable to cancel
|
||
'-4140': OrderImmediatelyFillable, # Invalid symbol status for opening position
|
||
'-4141': BadRequest, # Symbol is closed
|
||
'-4142': OrderImmediatelyFillable, # REJECT: take profit or stop order will be triggered immediately
|
||
'-4144': BadSymbol, # Invalid pair
|
||
'-4161': OperationRejected, # Leverage reduction is not supported in Isolated Margin Mode with open positions
|
||
'-4164': InvalidOrder, # Order's notional must be no smaller than 5.0(unless you choose reduce only)
|
||
'-4165': BadRequest, # Invalid time interval
|
||
'-4183': InvalidOrder, # Price is higher than stop price multiplier cap.
|
||
'-4184': InvalidOrder, # Price is lower than stop price multiplier floor.
|
||
'-4408': InvalidOrder, # This symbol is in reduce only mode due to regulation requirements. Please upgrade to Binance Credits Trading Mode.
|
||
#
|
||
# 5xxx Order Execution Issues
|
||
#
|
||
'-5021': OrderNotFillable, # Due to the order could not be filled immediately, the FOK order has been rejected.
|
||
'-5022': OrderNotFillable, # Due to the order could not be executed, the Post Only order will be rejected.
|
||
'-5028': OperationFailed, # The requested timestamp is outside the recvWindow of the matching engine
|
||
'-5041': RateLimitExceeded, # Time out for too many requests from self account queueing at the same time.
|
||
},
|
||
},
|
||
'exact': {
|
||
# error codes to cover ALL market types(however, specific market type might have override)
|
||
#
|
||
# 1xxx
|
||
#
|
||
'-1000': OperationFailed, # {"code":-1000,"msg":"An unknown error occured while processing the request."}
|
||
'-1001': OperationFailed, # {"code":-1001,"msg":"'Internal error; unable to process your request. Please try again.'"}
|
||
'-1002': AuthenticationError, # {"code":-1002,"msg":"'You are not authorized to execute self request.'"}
|
||
'-1003': RateLimitExceeded, # {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."}
|
||
'-1004': OperationRejected, # DUPLICATE_IP : This IP is already on the white list
|
||
'-1006': OperationFailed, # {"code":-1006,"msg":"An unexpected response was received from the message bus. Execution status unknown."}
|
||
'-1007': RequestTimeout, # {"code":-1007,"msg":"Timeout waiting for response from backend server. Send status unknown; execution status unknown."}
|
||
'-1010': OperationFailed, # {"code":-1010,"msg":"ERROR_MSG_RECEIVED."}
|
||
'-1013': BadRequest, # INVALID_MESSAGE
|
||
'-1014': InvalidOrder, # {"code":-1014,"msg":"Unsupported order combination."}
|
||
'-1015': RateLimitExceeded, # {"code":-1015,"msg":"'Too many new orders; current limit is %s orders per %s.'"}
|
||
'-1016': BadRequest, # {"code":-1016,"msg":"'This service is no longer available.',"}
|
||
'-1020': BadRequest, # {"code":-1020,"msg":"'This operation is not supported.'"}
|
||
'-1021': InvalidNonce, # {"code":-1021,"msg":"'your time is ahead of server'"}
|
||
'-1022': AuthenticationError, # {"code":-1022,"msg":"Signature for self request is not valid."}
|
||
'-1100': BadRequest, # {"code":-1100,"msg":"createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'"}
|
||
'-1101': BadRequest, # {"code":-1101,"msg":"Too many parameters; expected %s and received %s."}
|
||
'-1102': BadRequest, # {"code":-1102,"msg":"Param %s or %s must be sent, but both were empty"}
|
||
'-1103': BadRequest, # {"code":-1103,"msg":"An unknown parameter was sent."}
|
||
'-1104': BadRequest, # {"code":-1104,"msg":"Not all sent parameters were read, read 8 parameters but was sent 9"}
|
||
'-1105': BadRequest, # {"code":-1105,"msg":"Parameter %s was empty."}
|
||
'-1106': BadRequest, # {"code":-1106,"msg":"Parameter %s sent when not required."}
|
||
'-1108': BadSymbol, # {"code":-1108,"msg":"Invalid asset."}
|
||
'-1111': BadRequest, # {"code":-1111,"msg":"Precision is over the maximum defined for self asset."}
|
||
'-1112': OperationFailed, # {"code":-1112,"msg":"No orders on book for symbol."}
|
||
'-1114': BadRequest, # {"code":-1114,"msg":"TimeInForce parameter sent when not required."}
|
||
'-1115': BadRequest, # {"code":-1115,"msg":"Invalid timeInForce."}
|
||
'-1116': BadRequest, # {"code":-1116,"msg":"Invalid orderType."}
|
||
'-1117': BadRequest, # {"code":-1117,"msg":"Invalid side."}
|
||
'-1118': BadRequest, # {"code":-1118,"msg":"New client order ID was empty."}
|
||
'-1119': BadRequest, # {"code":-1119,"msg":"Original client order ID was empty."}
|
||
'-1120': BadRequest, # {"code":-1120,"msg":"Invalid interval."}
|
||
'-1121': BadSymbol, # {"code":-1121,"msg":"Invalid symbol."}
|
||
'-1125': AuthenticationError, # {"code":-1125,"msg":"This listenKey does not exist."}
|
||
'-1127': BadRequest, # {"code":-1127,"msg":"More than %s hours between startTime and endTime."}
|
||
'-1128': BadRequest, # {"code":-1128,"msg":"Combination of optional parameters invalid."}
|
||
'-1130': BadRequest, # {"code":-1130,"msg":"Data sent for paramter %s is not valid."}
|
||
#
|
||
# 2xxx
|
||
#
|
||
'-2010': InvalidOrder, # NEW_ORDER_REJECTED
|
||
'-2011': OrderNotFound, # {"code":-2011,"msg":"cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'"}
|
||
'-2013': OrderNotFound, # {"code":-2013,"msg":"fetchOrder(1, 'BTC/USDT') -> 'Order does not exist'"}
|
||
'-2014': AuthenticationError, # {"code":-2014,"msg":"API-key format invalid."}
|
||
'-2015': AuthenticationError, # {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
|
||
#
|
||
# 4xxx(common for linear, inverse, pm)
|
||
#
|
||
'-4000': InvalidOrder, # INVALID_ORDER_STATUS
|
||
'-4001': BadRequest, # PRICE_LESS_THAN_ZERO
|
||
'-4002': BadRequest, # PRICE_GREATER_THAN_MAX_PRICE
|
||
'-4003': BadRequest, # QTY_LESS_THAN_ZERO
|
||
'-4004': BadRequest, # QTY_LESS_THAN_MIN_QTY
|
||
'-4005': BadRequest, # QTY_GREATER_THAN_MAX_QTY
|
||
'-4006': BadRequest, # STOP_PRICE_LESS_THAN_ZERO
|
||
'-4007': BadRequest, # STOP_PRICE_GREATER_THAN_MAX_PRICE
|
||
'-4008': BadRequest, # TICK SIZE LESS THAN ZERO
|
||
'-4009': BadRequest, # MAX_PRICE_LESS_THAN_MIN_PRICE
|
||
'-4010': BadRequest, # MAX_QTY_LESS_THAN_MIN_QTY
|
||
'-4011': BadRequest, # STEP_SIZE_LESS_THAN_ZERO
|
||
'-4012': BadRequest, # MAX_NUM_ORDERS_LESS_THAN_ZERO
|
||
'-4013': BadRequest, # PRICE_LESS_THAN_MIN_PRICE
|
||
'-4014': BadRequest, # PRICE NOT INCREASED BY TICK SIZE
|
||
'-4015': BadRequest, # Client order id is not valid
|
||
'-4016': BadRequest, # Price is higher than mark price multiplier cap.
|
||
'-4017': BadRequest, # MULTIPLIER_UP_LESS_THAN_ZERO
|
||
'-4018': BadRequest, # MULTIPLIER_DOWN_LESS_THAN_ZERO
|
||
'-4019': OperationRejected, # COMPOSITE_SCALE_OVERFLOW
|
||
'-4020': BadRequest, # TARGET_STRATEGY_INVALID
|
||
'-4021': BadRequest, # INVALID_DEPTH_LIMIT
|
||
'-4022': BadRequest, # WRONG_MARKET_STATUS
|
||
'-4023': BadRequest, # QTY_NOT_INCREASED_BY_STEP_SIZE
|
||
'-4024': BadRequest, # PRICE_LOWER_THAN_MULTIPLIER_DOWN
|
||
'-4025': BadRequest, # MULTIPLIER_DECIMAL_LESS_THAN_ZERO
|
||
'-4026': BadRequest, # COMMISSION_INVALID
|
||
'-4027': BadRequest, # INVALID_ACCOUNT_TYPE
|
||
'-4028': BadRequest, # INVALID_LEVERAGE
|
||
'-4029': BadRequest, # INVALID_TICK SIZE_PRECISION
|
||
'-4030': BadRequest, # INVALID_STEP_SIZE_PRECISION
|
||
'-4031': BadRequest, # INVALID_WORKING_TYPE
|
||
'-4032': OperationRejected, # EXCEED_MAX_CANCEL_ORDER_SIZE(or Invalid parameter working type: %s)
|
||
'-4033': BadRequest, # INSURANCE_ACCOUNT_NOT_FOUND
|
||
'-4044': BadRequest, # INVALID_BALANCE_TYPE
|
||
'-4045': OperationRejected, # MAX_STOP_ORDER_EXCEEDED
|
||
'-4046': OperationRejected, # NO_NEED_TO_CHANGE_MARGIN_TYPE
|
||
'-4047': OperationRejected, # Margin type cannot be changed if there exists open orders.
|
||
'-4048': OperationRejected, # Margin type cannot be changed if there exists position.
|
||
'-4049': BadRequest, # Add margin only support for isolated position.
|
||
'-4050': InsufficientFunds, # Cross balance insufficient
|
||
'-4051': InsufficientFunds, # Isolated balance insufficient.
|
||
'-4052': OperationRejected, # No need to change auto add margin.
|
||
'-4053': BadRequest, # Auto add margin only support for isolated position.
|
||
'-4054': OperationRejected, # Cannot add position margin: position is 0.
|
||
'-4055': BadRequest, # Amount must be positive.
|
||
'-4056': AuthenticationError, # INVALID_API_KEY_TYPE
|
||
'-4057': AuthenticationError, # INVALID_RSA_PUBLIC_KEY: Invalid api public key
|
||
'-4058': BadRequest, # MAX_PRICE_TOO_LARGE
|
||
'-4059': OperationRejected, # NO_NEED_TO_CHANGE_POSITION_SIDE
|
||
'-4060': BadRequest, # INVALID_POSITION_SIDE
|
||
'-4061': OperationRejected, # POSITION_SIDE_NOT_MATCH: Order's position side does not match user's setting.
|
||
'-4062': BadRequest, # REDUCE_ONLY_CONFLICT: Invalid or improper reduceOnly value.
|
||
'-4067': OperationRejected, # Position side cannot be changed if there exists open orders.
|
||
'-4068': OperationRejected, # Position side cannot be changed if there exists position.
|
||
'-4082': BadRequest, # Invalid number of batch place orders.
|
||
'-4083': OperationRejected, # PLACE_BATCH_ORDERS_FAIL : Fail to place batch orders.
|
||
'-4084': BadRequest, # UPCOMING_METHOD : Method is not allowed currently. Upcoming soon.
|
||
'-4086': BadRequest, # Invalid price spread threshold.
|
||
'-4104': BadRequest, # INVALID_CONTRACT_TYPE
|
||
'-4135': BadRequest, # Invalid activation price
|
||
'-4137': BadRequest, # Quantity must be zero with closePosition equals True
|
||
'-4138': BadRequest, # Reduce only must be True with closePosition equals True
|
||
'-4139': BadRequest, # Order type can not be market if it's unable to cancel
|
||
'-4142': OrderImmediatelyFillable, # REJECT: take profit or stop order will be triggered immediately
|
||
#
|
||
# 2xxxx
|
||
#
|
||
# 20xxx - spot & futures algo(TBD for OPTIONS & PORTFOLIO MARGIN)
|
||
'-20121': BadSymbol, # Invalid symbol.
|
||
'-20124': BadRequest, # Invalid algo id or it has been completed.
|
||
'-20130': BadRequest, # Invalid data sent for a parameter
|
||
'-20132': BadRequest, # The client algo id is duplicated
|
||
'-20194': BadRequest, # Duration is too short to execute all required quantity.
|
||
'-20195': BadRequest, # The total size is too small.
|
||
'-20196': BadRequest, # The total size is too large.
|
||
'-20198': OperationRejected, # Reach the max open orders allowed.
|
||
'-20204': BadRequest, # The notional of USD is less or more than the limit.
|
||
#
|
||
# strings
|
||
#
|
||
'System is under maintenance.': OnMaintenance, # {"code":1,"msg":"System is under maintenance."}
|
||
'System abnormality': OperationFailed, # {"code":-1000,"msg":"System abnormality"}
|
||
'You are not authorized to execute self request.': PermissionDenied, # {"msg":"You are not authorized to execute self request."}
|
||
'API key does not exist': AuthenticationError,
|
||
'Order would trigger immediately.': OrderImmediatelyFillable,
|
||
'Stop price would trigger immediately.': OrderImmediatelyFillable, # {"code":-2010,"msg":"Stop price would trigger immediately."}
|
||
'Order would immediately match and take.': OrderImmediatelyFillable, # {"code":-2010,"msg":"Order would immediately match and take."}
|
||
'Account has insufficient balance for requested action.': InsufficientFunds,
|
||
'Rest API trading is not enabled.': PermissionDenied,
|
||
'This account may not place or cancel orders.': PermissionDenied,
|
||
"You don't have permission.": PermissionDenied, # {"msg":"You don't have permission.","success":false}
|
||
'Market is closed.': MarketClosed, # {"code":-1013,"msg":"Market is closed."}
|
||
'Too many requests. Please try again later.': RateLimitExceeded, # {"msg":"Too many requests. Please try again later.","success":false}
|
||
'This action is disabled on self account.': AccountSuspended, # {"code":-2011,"msg":"This action is disabled on self account."}
|
||
'Limit orders require GTC for self phase.': BadRequest,
|
||
'This order type is not hasattr(self, possible) trading phase.': BadRequest,
|
||
'This type of sub-account exceeds the maximum number limit': OperationRejected, # {"code":-9000,"msg":"This type of sub-account exceeds the maximum number limit"}
|
||
'This symbol is restricted for self account.': PermissionDenied,
|
||
'This symbol is not permitted for self account.': PermissionDenied, # {"code":-2010,"msg":"This symbol is not permitted for self account."}
|
||
},
|
||
'broad': {
|
||
'has no operation privilege': PermissionDenied,
|
||
'MAX_POSITION': BadRequest, # {"code":-2010,"msg":"Filter failure: MAX_POSITION"}
|
||
},
|
||
},
|
||
})
|
||
|
||
def is_inverse(self, type: str, subType: Str = None) -> bool:
|
||
if subType is None:
|
||
return(type == 'delivery')
|
||
else:
|
||
return subType == 'inverse'
|
||
|
||
def is_linear(self, type: str, subType: Str = None) -> bool:
|
||
if subType is None:
|
||
return(type == 'future') or (type == 'swap')
|
||
else:
|
||
return subType == 'linear'
|
||
|
||
def set_sandbox_mode(self, enable: bool):
|
||
super(binance, self).set_sandbox_mode(enable)
|
||
self.options['sandboxMode'] = enable
|
||
|
||
def create_expired_option_market(self, symbol: str):
|
||
# support expired option contracts
|
||
settle = 'USDT'
|
||
optionParts = symbol.split('-')
|
||
symbolBase = symbol.split('/')
|
||
base = None
|
||
if symbol.find('/') > -1:
|
||
base = self.safe_string(symbolBase, 0)
|
||
else:
|
||
base = self.safe_string(optionParts, 0)
|
||
expiry = self.safe_string(optionParts, 1)
|
||
strike = self.safe_integer(optionParts, 2)
|
||
strikeAsString = self.safe_string(optionParts, 2)
|
||
optionType = self.safe_string(optionParts, 3)
|
||
datetime = self.convert_expire_date(expiry)
|
||
timestamp = self.parse8601(datetime)
|
||
return {
|
||
'id': base + '-' + expiry + '-' + strikeAsString + '-' + optionType,
|
||
'symbol': base + '/' + settle + ':' + settle + '-' + expiry + '-' + strikeAsString + '-' + optionType,
|
||
'base': base,
|
||
'quote': settle,
|
||
'baseId': base,
|
||
'quoteId': settle,
|
||
'active': None,
|
||
'type': 'option',
|
||
'linear': None,
|
||
'inverse': None,
|
||
'spot': False,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': True,
|
||
'margin': False,
|
||
'contract': True,
|
||
'contractSize': None,
|
||
'expiry': timestamp,
|
||
'expiryDatetime': datetime,
|
||
'optionType': 'call' if (optionType == 'C') else 'put',
|
||
'strike': strike,
|
||
'settle': settle,
|
||
'settleId': settle,
|
||
'precision': {
|
||
'amount': None,
|
||
'price': None,
|
||
},
|
||
'limits': {
|
||
'amount': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'info': None,
|
||
}
|
||
|
||
def market(self, symbol: str) -> MarketInterface:
|
||
if self.markets is None:
|
||
raise ExchangeError(self.id + ' markets not loaded')
|
||
# defaultType has legacy support on binance
|
||
defaultType = self.safe_string(self.options, 'defaultType')
|
||
defaultSubType = self.safe_string(self.options, 'defaultSubType')
|
||
isLegacyLinear = defaultType == 'future'
|
||
isLegacyInverse = defaultType == 'delivery'
|
||
isLegacy = isLegacyLinear or isLegacyInverse
|
||
if isinstance(symbol, str):
|
||
if symbol in self.markets:
|
||
market = self.markets[symbol]
|
||
# begin diff
|
||
if isLegacy and market['spot']:
|
||
settle = market['quote'] if isLegacyLinear else market['base']
|
||
futuresSymbol = symbol + ':' + settle
|
||
if futuresSymbol in self.markets:
|
||
return self.markets[futuresSymbol]
|
||
else:
|
||
return market
|
||
# end diff
|
||
elif symbol in self.markets_by_id:
|
||
markets = self.markets_by_id[symbol]
|
||
# begin diff
|
||
if isLegacyLinear:
|
||
defaultType = 'linear'
|
||
elif isLegacyInverse:
|
||
defaultType = 'inverse'
|
||
elif defaultType is None:
|
||
defaultType = defaultSubType
|
||
# end diff
|
||
for i in range(0, len(markets)):
|
||
market = markets[i]
|
||
if market[defaultType]:
|
||
return market
|
||
return markets[0]
|
||
elif (symbol.find('/') > -1) and (symbol.find(':') < 0):
|
||
if (defaultType is not None) and (defaultType != 'spot'):
|
||
# support legacy symbols
|
||
base, quote = symbol.split('/')
|
||
settle = base if (quote == 'USD') else quote
|
||
futuresSymbol = symbol + ':' + settle
|
||
if futuresSymbol in self.markets:
|
||
return self.markets[futuresSymbol]
|
||
elif (symbol.find('-C') > -1) or (symbol.find('-P') > -1): # both exchange-id and unified symbols are supported self way regardless of the defaultType
|
||
return self.create_expired_option_market(symbol)
|
||
raise BadSymbol(self.id + ' does not have market symbol ' + symbol)
|
||
|
||
def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
|
||
isOption = (marketId is not None) and ((marketId.find('-C') > -1) or (marketId.find('-P') > -1))
|
||
if isOption and not (marketId in self.markets_by_id):
|
||
# handle expired option contracts
|
||
return self.create_expired_option_market(marketId)
|
||
return super(binance, self).safe_market(marketId, market, delimiter, marketType)
|
||
|
||
def cost_to_precision(self, symbol, cost):
|
||
return self.decimal_to_precision(cost, TRUNCATE, self.markets[symbol]['precision']['quote'], self.precisionMode, self.paddingMode)
|
||
|
||
def nonce(self):
|
||
return self.milliseconds() - self.options['timeDifference']
|
||
|
||
def enable_demo_trading(self, enable: bool):
|
||
"""
|
||
enables or disables demo trading mode
|
||
|
||
https://www.binance.com/en/support/faq/detail/9be58f73e5e14338809e3b705b9687dd
|
||
https://demo.binance.com/en/my/settings/api-management
|
||
|
||
:param boolean [enable]: True if demo trading should be enabled, False otherwise
|
||
"""
|
||
if self.isSandboxModeEnabled:
|
||
raise NotSupported(self.id + ' demo trading is not supported in the sandbox environment. Please check https://www.binance.com/en/support/faq/detail/9be58f73e5e14338809e3b705b9687dd to see the differences')
|
||
if enable:
|
||
self.urls['apiBackupDemoTrading'] = self.urls['api']
|
||
self.urls['api'] = self.urls['demo']
|
||
elif 'apiBackupDemoTrading' in self.urls:
|
||
self.urls['api'] = self.urls['apiBackupDemoTrading']
|
||
newUrls = self.omit(self.urls, 'apiBackupDemoTrading')
|
||
self.urls = newUrls
|
||
self.options['enableDemoTrading'] = enable
|
||
|
||
def fetch_time(self, params={}) -> Int:
|
||
"""
|
||
fetches the current integer timestamp in milliseconds from the exchange server
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/general-endpoints#check-server-time # spot
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Check-Server-Time # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Check-Server-time # future
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns int: the current integer timestamp in milliseconds from the exchange server
|
||
"""
|
||
defaultType = self.safe_string_2(self.options, 'fetchTime', 'defaultType', 'spot')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
query = self.omit(params, 'type')
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchTime', None, params)
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicGetTime(query)
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetTime(query)
|
||
else:
|
||
response = self.publicGetTime(query)
|
||
return self.safe_integer(response, 'serverTime')
|
||
|
||
def fetch_currencies(self, params={}) -> Currencies:
|
||
"""
|
||
fetches all available currencies on an exchange
|
||
|
||
https://developers.binance.com/docs/wallet/capital/all-coins-info
|
||
https://developers.binance.com/docs/margin_trading/market-data/Get-All-Margin-Assets
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an associative dictionary of currencies
|
||
"""
|
||
fetchCurrenciesEnabled = self.safe_bool(self.options, 'fetchCurrencies')
|
||
if not fetchCurrenciesEnabled:
|
||
return {}
|
||
# self endpoint requires authentication
|
||
# while fetchCurrencies is a public API method by design
|
||
# therefore we check the keys here
|
||
# and fallback to generating the currencies from the markets
|
||
if not self.check_required_credentials(False):
|
||
return {}
|
||
# sandbox/testnet does not support sapi endpoints
|
||
apiBackup = self.safe_value(self.urls, 'apiBackup')
|
||
if apiBackup is not None:
|
||
return {}
|
||
# demotrading does not support sapi endpoints
|
||
if self.safe_bool(self.options, 'enableDemoTrading', False):
|
||
return {}
|
||
promises = [self.sapiGetCapitalConfigGetall(params)]
|
||
fetchMargins = self.safe_bool(self.options, 'fetchMargins', False)
|
||
if fetchMargins:
|
||
promises.append(self.sapiGetMarginAllPairs(params))
|
||
results = promises
|
||
responseCurrencies = results[0]
|
||
marginablesById = None
|
||
if fetchMargins:
|
||
responseMarginables = results[1]
|
||
marginablesById = self.index_by(responseMarginables, 'assetName')
|
||
result: dict = {}
|
||
for i in range(0, len(responseCurrencies)):
|
||
#
|
||
# {
|
||
# "coin": "LINK",
|
||
# "depositAllEnable": True,
|
||
# "withdrawAllEnable": True,
|
||
# "name": "ChainLink",
|
||
# "free": "0",
|
||
# "locked": "0",
|
||
# "freeze": "0",
|
||
# "withdrawing": "0",
|
||
# "ipoing": "0",
|
||
# "ipoable": "0",
|
||
# "storage": "0",
|
||
# "isLegalMoney": False,
|
||
# "trading": True,
|
||
# "networkList": [
|
||
# {
|
||
# "network": "BSC",
|
||
# "coin": "LINK",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": False,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": "",
|
||
# "withdrawDesc": "",
|
||
# "specialTips": "",
|
||
# "specialWithdrawTips": "The network you have selected is BSC. Please ensure that the withdrawal address supports the Binance Smart Chain network. You will lose your assets if the chosen platform does not support retrievals.",
|
||
# "name": "BNB Smart Chain(BEP20)",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(0x)[0-9A-Fa-f]{40}$",
|
||
# "addressRule": "",
|
||
# "memoRegex": "",
|
||
# "withdrawFee": "0.012",
|
||
# "withdrawMin": "0.024",
|
||
# "withdrawMax": "9999999999.99999999",
|
||
# "minConfirm": "15",
|
||
# "unLockConfirm": "0",
|
||
# "sameAddress": False,
|
||
# "estimatedArrivalTime": "5",
|
||
# "busy": False,
|
||
# "country": "AE,BINANCE_BAHRAIN_BSC"
|
||
# },
|
||
# {
|
||
# "network": "BNB",
|
||
# "coin": "LINK",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": False,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": "",
|
||
# "withdrawDesc": "",
|
||
# "specialTips": "Both a MEMO and an Address are required to successfully deposit your LINK BEP2 tokens to Binance.",
|
||
# "specialWithdrawTips": "",
|
||
# "name": "BNB Beacon Chain(BEP2)",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(bnb1)[0-9a-z]{38}$",
|
||
# "addressRule": "",
|
||
# "memoRegex": "^[0-9A-Za-z\\-_]{1,120}$",
|
||
# "withdrawFee": "0.003",
|
||
# "withdrawMin": "0.01",
|
||
# "withdrawMax": "10000000000",
|
||
# "minConfirm": "1",
|
||
# "unLockConfirm": "0",
|
||
# "sameAddress": True,
|
||
# "estimatedArrivalTime": "5",
|
||
# "busy": False,
|
||
# "country": "AE,BINANCE_BAHRAIN_BSC"
|
||
# },
|
||
# {
|
||
# "network": "ETH",
|
||
# "coin": "LINK",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": True,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": "",
|
||
# "withdrawDesc": "",
|
||
# "name": "Ethereum(ERC20)",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(0x)[0-9A-Fa-f]{40}$",
|
||
# "addressRule": "",
|
||
# "memoRegex": "",
|
||
# "withdrawFee": "0.55",
|
||
# "withdrawMin": "1.1",
|
||
# "withdrawMax": "10000000000",
|
||
# "minConfirm": "12",
|
||
# "unLockConfirm": "0",
|
||
# "sameAddress": False,
|
||
# "estimatedArrivalTime": "5",
|
||
# "busy": False,
|
||
# "country": "AE,BINANCE_BAHRAIN_BSC"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
entry = responseCurrencies[i]
|
||
id = self.safe_string(entry, 'coin')
|
||
name = self.safe_string(entry, 'name')
|
||
code = self.safe_currency_code(id)
|
||
isFiat = self.safe_bool(entry, 'isLegalMoney')
|
||
minPrecision = None
|
||
isWithdrawEnabled = True
|
||
isDepositEnabled = True
|
||
networkList = self.safe_list(entry, 'networkList', [])
|
||
fees: dict = {}
|
||
fee = None
|
||
networks: dict = {}
|
||
for j in range(0, len(networkList)):
|
||
networkItem = networkList[j]
|
||
network = self.safe_string(networkItem, 'network')
|
||
networkCode = self.network_id_to_code(network)
|
||
isETF = (network == 'ETF') # e.g. BTCUP, ETHDOWN
|
||
# name = self.safe_string(networkItem, 'name')
|
||
withdrawFee = self.safe_number(networkItem, 'withdrawFee')
|
||
depositEnable = self.safe_bool(networkItem, 'depositEnable')
|
||
withdrawEnable = self.safe_bool(networkItem, 'withdrawEnable')
|
||
isDepositEnabled = isDepositEnabled or depositEnable
|
||
isWithdrawEnabled = isWithdrawEnabled or withdrawEnable
|
||
fees[network] = withdrawFee
|
||
isDefault = self.safe_bool(networkItem, 'isDefault')
|
||
if isDefault or (fee is None):
|
||
fee = withdrawFee
|
||
# todo: default networks in "setMarkets" overload
|
||
# if isDefault:
|
||
# self.options['defaultNetworkCodesForCurrencies'][code] = networkCode
|
||
# }
|
||
precisionTick = self.safe_string(networkItem, 'withdrawIntegerMultiple')
|
||
withdrawPrecision = precisionTick
|
||
# avoid zero values, which are mostly from fiat or leveraged tokens or some abandoned coins : https://github.com/ccxt/ccxt/pull/14902#issuecomment-1271636731
|
||
if not Precise.string_eq(precisionTick, '0'):
|
||
minPrecision = precisionTick if (minPrecision is None) else Precise.string_min(minPrecision, precisionTick)
|
||
else:
|
||
if not isFiat and not isETF:
|
||
# non-fiat and non-ETF currency, there are many cases when precision is set to zero(probably bug, we've reported to binance already)
|
||
# in such cases, we can set default precision of 8(which is in UI for such coins)
|
||
withdrawPrecision = self.omit_zero(self.safe_string(networkItem, 'withdrawInternalMin'))
|
||
if withdrawPrecision is None:
|
||
withdrawPrecision = self.safe_string(self.options, 'defaultWithdrawPrecision')
|
||
networks[networkCode] = {
|
||
'info': networkItem,
|
||
'id': network,
|
||
'network': networkCode,
|
||
'active': depositEnable and withdrawEnable,
|
||
'deposit': depositEnable,
|
||
'withdraw': withdrawEnable,
|
||
'fee': withdrawFee,
|
||
'precision': self.parse_number(withdrawPrecision),
|
||
'limits': {
|
||
'withdraw': {
|
||
'min': self.safe_number(networkItem, 'withdrawMin'),
|
||
'max': self.safe_number(networkItem, 'withdrawMax'),
|
||
},
|
||
'deposit': {
|
||
'min': self.safe_number(networkItem, 'depositDust'),
|
||
'max': None,
|
||
},
|
||
},
|
||
}
|
||
trading = self.safe_bool(entry, 'trading')
|
||
active = (isWithdrawEnabled and isDepositEnabled and trading)
|
||
marginEntry = self.safe_dict(marginablesById, id, {})
|
||
#
|
||
# {
|
||
# assetName: "BTC",
|
||
# assetFullName: "Bitcoin",
|
||
# isBorrowable: True,
|
||
# isMortgageable: True,
|
||
# userMinBorrow: "0",
|
||
# userMinRepay: "0",
|
||
# }
|
||
#
|
||
result[code] = {
|
||
'id': id,
|
||
'name': name,
|
||
'code': code,
|
||
'type': 'fiat' if isFiat else 'crypto',
|
||
'precision': self.parse_number(minPrecision),
|
||
'info': entry,
|
||
'active': active,
|
||
'deposit': isDepositEnabled,
|
||
'withdraw': isWithdrawEnabled,
|
||
'networks': networks,
|
||
'fee': fee,
|
||
'fees': fees,
|
||
'limits': self.limits,
|
||
'margin': self.safe_bool(marginEntry, 'isBorrowable'),
|
||
}
|
||
return result
|
||
|
||
def fetch_markets(self, params={}) -> List[Market]:
|
||
"""
|
||
retrieves data on all markets for binance
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/general-endpoints#exchange-information # spot
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Exchange-Information # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Exchange-Information # future
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Exchange-Information # option
|
||
https://developers.binance.com/docs/margin_trading/market-data/Get-All-Cross-Margin-Pairs # cross margin
|
||
https://developers.binance.com/docs/margin_trading/market-data/Get-All-Isolated-Margin-Symbol # isolated margin
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: an array of objects representing market data
|
||
"""
|
||
promisesRaw = []
|
||
rawFetchMarkets = None
|
||
defaultTypes = ['spot', 'linear', 'inverse']
|
||
fetchMarketsOptions = self.safe_dict(self.options, 'fetchMarkets')
|
||
if fetchMarketsOptions is not None:
|
||
rawFetchMarkets = self.safe_list(fetchMarketsOptions, 'types', defaultTypes)
|
||
else:
|
||
# for backward-compatibility
|
||
rawFetchMarkets = self.safe_list(self.options, 'fetchMarkets', defaultTypes)
|
||
# handle loadAllOptions option
|
||
loadAllOptions = self.safe_bool(self.options, 'loadAllOptions', False)
|
||
if loadAllOptions:
|
||
if not self.in_array('option', rawFetchMarkets):
|
||
rawFetchMarkets.append('option')
|
||
sandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
|
||
demoMode = self.safe_bool(self.options, 'enableDemoTrading', False)
|
||
isDemoEnv = demoMode or sandboxMode
|
||
fetchMarkets = []
|
||
for i in range(0, len(rawFetchMarkets)):
|
||
type = rawFetchMarkets[i]
|
||
if type == 'option' and isDemoEnv:
|
||
continue
|
||
fetchMarkets.append(type)
|
||
fetchMargins = self.safe_bool(self.options, 'fetchMargins', False)
|
||
for i in range(0, len(fetchMarkets)):
|
||
marketType = fetchMarkets[i]
|
||
if marketType == 'spot':
|
||
promisesRaw.append(self.publicGetExchangeInfo(params))
|
||
if fetchMargins and self.check_required_credentials(False) and not isDemoEnv:
|
||
promisesRaw.append(self.sapiGetMarginAllPairs(params))
|
||
promisesRaw.append(self.sapiGetMarginIsolatedAllPairs(params))
|
||
elif marketType == 'linear':
|
||
promisesRaw.append(self.fapiPublicGetExchangeInfo(params))
|
||
elif marketType == 'inverse':
|
||
promisesRaw.append(self.dapiPublicGetExchangeInfo(params))
|
||
elif marketType == 'option':
|
||
promisesRaw.append(self.eapiPublicGetExchangeInfo(params))
|
||
else:
|
||
raise ExchangeError(self.id + ' fetchMarkets() self.options fetchMarkets "' + marketType + '" is not a supported market type')
|
||
results = promisesRaw
|
||
markets = []
|
||
self.options['crossMarginPairsData'] = []
|
||
self.options['isolatedMarginPairsData'] = []
|
||
for i in range(0, len(results)):
|
||
res = self.safe_value(results, i)
|
||
if fetchMargins and isinstance(res, list):
|
||
keysList = list(self.index_by(res, 'symbol').keys())
|
||
length = len(self.options['crossMarginPairsData'])
|
||
# first one is the cross-margin promise
|
||
if length == 0:
|
||
self.options['crossMarginPairsData'] = keysList
|
||
else:
|
||
self.options['isolatedMarginPairsData'] = keysList
|
||
else:
|
||
resultMarkets = self.safe_list_2(res, 'symbols', 'optionSymbols', [])
|
||
markets = self.array_concat(markets, resultMarkets)
|
||
#
|
||
# spot / margin
|
||
#
|
||
# {
|
||
# "timezone":"UTC",
|
||
# "serverTime":1575416692969,
|
||
# "rateLimits":[
|
||
# {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},
|
||
# {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":100},
|
||
# {"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":200000}
|
||
# ],
|
||
# "exchangeFilters":[],
|
||
# "symbols":[
|
||
# {
|
||
# "symbol":"ETHBTC",
|
||
# "status":"TRADING",
|
||
# "baseAsset":"ETH",
|
||
# "baseAssetPrecision":8,
|
||
# "quoteAsset":"BTC",
|
||
# "quotePrecision":8,
|
||
# "baseCommissionPrecision":8,
|
||
# "quoteCommissionPrecision":8,
|
||
# "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"],
|
||
# "icebergAllowed":true,
|
||
# "ocoAllowed":true,
|
||
# "quoteOrderQtyMarketAllowed":true,
|
||
# "allowTrailingStop":false,
|
||
# "isSpotTradingAllowed":true,
|
||
# "isMarginTradingAllowed":true,
|
||
# "filters":[
|
||
# {"filterType":"PRICE_FILTER","minPrice":"0.00000100","maxPrice":"100000.00000000","tickSize":"0.00000100"},
|
||
# {"filterType":"PERCENT_PRICE","multiplierUp":"5","multiplierDown":"0.2","avgPriceMins":5},
|
||
# {"filterType":"LOT_SIZE","minQty":"0.00100000","maxQty":"100000.00000000","stepSize":"0.00100000"},
|
||
# {"filterType":"MIN_NOTIONAL","minNotional":"0.00010000","applyToMarket":true,"avgPriceMins":5},
|
||
# {"filterType":"ICEBERG_PARTS","limit":10},
|
||
# {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"63100.00000000","stepSize":"0.00000000"},
|
||
# {"filterType":"MAX_NUM_ORDERS","maxNumOrders":200},
|
||
# {"filterType":"MAX_NUM_ALGO_ORDERS","maxNumAlgoOrders":5}
|
||
# ],
|
||
# "permissions":["SPOT","MARGIN"]}
|
||
# },
|
||
# ],
|
||
# }
|
||
#
|
||
# cross & isolated pairs response:
|
||
#
|
||
# [
|
||
# {
|
||
# symbol: "BTCUSDT",
|
||
# base: "BTC",
|
||
# quote: "USDT",
|
||
# isMarginTrade: True,
|
||
# isBuyAllowed: True,
|
||
# isSellAllowed: True,
|
||
# id: "376870555451677893", # doesn't exist in isolated
|
||
# },
|
||
# ]
|
||
#
|
||
# futures/usdt-margined(fapi)
|
||
#
|
||
# {
|
||
# "timezone":"UTC",
|
||
# "serverTime":1575417244353,
|
||
# "rateLimits":[
|
||
# {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200},
|
||
# {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":1200}
|
||
# ],
|
||
# "exchangeFilters":[],
|
||
# "symbols":[
|
||
# {
|
||
# "symbol":"BTCUSDT",
|
||
# "status":"TRADING",
|
||
# "maintMarginPercent":"2.5000",
|
||
# "requiredMarginPercent":"5.0000",
|
||
# "baseAsset":"BTC",
|
||
# "quoteAsset":"USDT",
|
||
# "pricePrecision":2,
|
||
# "quantityPrecision":3,
|
||
# "baseAssetPrecision":8,
|
||
# "quotePrecision":8,
|
||
# "filters":[
|
||
# {"minPrice":"0.01","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.01"},
|
||
# {"stepSize":"0.001","filterType":"LOT_SIZE","maxQty":"1000","minQty":"0.001"},
|
||
# {"stepSize":"0.001","filterType":"MARKET_LOT_SIZE","maxQty":"1000","minQty":"0.001"},
|
||
# {"limit":200,"filterType":"MAX_NUM_ORDERS"},
|
||
# {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
|
||
# ],
|
||
# "orderTypes":["LIMIT","MARKET","STOP"],
|
||
# "timeInForce":["GTC","IOC","FOK","GTX"]
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
# delivery/coin-margined(dapi)
|
||
#
|
||
# {
|
||
# "timezone": "UTC",
|
||
# "serverTime": 1597667052958,
|
||
# "rateLimits": [
|
||
# {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":6000},
|
||
# {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":6000}
|
||
# ],
|
||
# "exchangeFilters": [],
|
||
# "symbols": [
|
||
# {
|
||
# "symbol": "BTCUSD_200925",
|
||
# "pair": "BTCUSD",
|
||
# "contractType": "CURRENT_QUARTER",
|
||
# "deliveryDate": 1601020800000,
|
||
# "onboardDate": 1590739200000,
|
||
# "contractStatus": "TRADING",
|
||
# "contractSize": 100,
|
||
# "marginAsset": "BTC",
|
||
# "maintMarginPercent": "2.5000",
|
||
# "requiredMarginPercent": "5.0000",
|
||
# "baseAsset": "BTC",
|
||
# "quoteAsset": "USD",
|
||
# "pricePrecision": 1,
|
||
# "quantityPrecision": 0,
|
||
# "baseAssetPrecision": 8,
|
||
# "quotePrecision": 8,
|
||
# "equalQtyPrecision": 4,
|
||
# "filters": [
|
||
# {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"},
|
||
# {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"},
|
||
# {"stepSize":"0","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"},
|
||
# {"limit":200,"filterType":"MAX_NUM_ORDERS"},
|
||
# {"multiplierDown":"0.9500","multiplierUp":"1.0500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
|
||
# ],
|
||
# "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"],
|
||
# "timeInForce": ["GTC","IOC","FOK","GTX"]
|
||
# },
|
||
# {
|
||
# "symbol": "BTCUSD_PERP",
|
||
# "pair": "BTCUSD",
|
||
# "contractType": "PERPETUAL",
|
||
# "deliveryDate": 4133404800000,
|
||
# "onboardDate": 1596006000000,
|
||
# "contractStatus": "TRADING",
|
||
# "contractSize": 100,
|
||
# "marginAsset": "BTC",
|
||
# "maintMarginPercent": "2.5000",
|
||
# "requiredMarginPercent": "5.0000",
|
||
# "baseAsset": "BTC",
|
||
# "quoteAsset": "USD",
|
||
# "pricePrecision": 1,
|
||
# "quantityPrecision": 0,
|
||
# "baseAssetPrecision": 8,
|
||
# "quotePrecision": 8,
|
||
# "equalQtyPrecision": 4,
|
||
# "filters": [
|
||
# {"minPrice":"0.1","maxPrice":"100000","filterType":"PRICE_FILTER","tickSize":"0.1"},
|
||
# {"stepSize":"1","filterType":"LOT_SIZE","maxQty":"100000","minQty":"1"},
|
||
# {"stepSize":"1","filterType":"MARKET_LOT_SIZE","maxQty":"100000","minQty":"1"},
|
||
# {"limit":200,"filterType":"MAX_NUM_ORDERS"},
|
||
# {"multiplierDown":"0.8500","multiplierUp":"1.1500","multiplierDecimal":"4","filterType":"PERCENT_PRICE"}
|
||
# ],
|
||
# "orderTypes": ["LIMIT","MARKET","STOP","STOP_MARKET","TAKE_PROFIT","TAKE_PROFIT_MARKET","TRAILING_STOP_MARKET"],
|
||
# "timeInForce": ["GTC","IOC","FOK","GTX"]
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# {
|
||
# "timezone": "UTC",
|
||
# "serverTime": 1675912490405,
|
||
# "optionContracts": [
|
||
# {
|
||
# "id": 1,
|
||
# "baseAsset": "SOL",
|
||
# "quoteAsset": "USDT",
|
||
# "underlying": "SOLUSDT",
|
||
# "settleAsset": "USDT"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "optionAssets": [
|
||
# {"id":1,"name":"USDT"}
|
||
# ],
|
||
# "optionSymbols": [
|
||
# {
|
||
# "contractId": 3,
|
||
# "expiryDate": 1677225600000,
|
||
# "filters": [
|
||
# {"filterType":"PRICE_FILTER","minPrice":"724.6","maxPrice":"919.2","tickSize":"0.1"},
|
||
# {"filterType":"LOT_SIZE","minQty":"0.01","maxQty":"1001","stepSize":"0.01"}
|
||
# ],
|
||
# "id": 2474,
|
||
# "symbol": "ETH-230224-800-C",
|
||
# "side": "CALL",
|
||
# "strikePrice": "800.00000000",
|
||
# "underlying": "ETHUSDT",
|
||
# "unit": 1,
|
||
# "makerFeeRate": "0.00020000",
|
||
# "takerFeeRate": "0.00020000",
|
||
# "minQty": "0.01",
|
||
# "maxQty": "1000",
|
||
# "initialMargin": "0.15000000",
|
||
# "maintenanceMargin": "0.07500000",
|
||
# "minInitialMargin": "0.10000000",
|
||
# "minMaintenanceMargin": "0.05000000",
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "quoteAsset": "USDT"
|
||
# },
|
||
# ...
|
||
# ],
|
||
# "rateLimits": [
|
||
# {"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":400},
|
||
# {"rateLimitType":"ORDERS","interval":"MINUTE","intervalNum":1,"limit":100},
|
||
# {"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":30}
|
||
# ]
|
||
# }
|
||
#
|
||
if self.options['adjustForTimeDifference']:
|
||
self.load_time_difference()
|
||
result = []
|
||
for i in range(0, len(markets)):
|
||
result.append(self.parse_market(markets[i]))
|
||
return result
|
||
|
||
def parse_market(self, market: dict) -> Market:
|
||
swap = False
|
||
future = False
|
||
option = False
|
||
underlying = self.safe_string(market, 'underlying')
|
||
id = self.safe_string(market, 'symbol')
|
||
optionParts = id.split('-')
|
||
optionBase = self.safe_string(optionParts, 0)
|
||
lowercaseId = self.safe_string_lower(market, 'symbol')
|
||
baseId = self.safe_string(market, 'baseAsset', optionBase)
|
||
quoteId = self.safe_string(market, 'quoteAsset')
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
contractType = self.safe_string(market, 'contractType')
|
||
contract = ('contractType' in market)
|
||
expiry = self.safe_integer_2(market, 'deliveryDate', 'expiryDate')
|
||
settleId = self.safe_string(market, 'marginAsset')
|
||
if (contractType == 'PERPETUAL') or (expiry == 4133404800000): # some swap markets do not have contract type, eg: BTCST
|
||
expiry = None
|
||
swap = True
|
||
elif underlying is not None:
|
||
contract = True
|
||
option = True
|
||
settleId = 'USDT' if (settleId is None) else settleId
|
||
elif expiry is not None:
|
||
future = True
|
||
settle = self.safe_currency_code(settleId)
|
||
spot = not contract
|
||
filters = self.safe_list(market, 'filters', [])
|
||
filtersByType = self.index_by(filters, 'filterType')
|
||
status = self.safe_string_2(market, 'status', 'contractStatus')
|
||
contractSize = None
|
||
fees = self.fees
|
||
linear = None
|
||
inverse = None
|
||
symbol = base + '/' + quote
|
||
strike = None
|
||
if contract:
|
||
if swap:
|
||
symbol = symbol + ':' + settle
|
||
elif future:
|
||
symbol = symbol + ':' + settle + '-' + self.yymmdd(expiry)
|
||
elif option:
|
||
strike = self.number_to_string(self.parse_to_numeric(self.safe_string(market, 'strikePrice')))
|
||
symbol = symbol + ':' + settle + '-' + self.yymmdd(expiry) + '-' + strike + '-' + self.safe_string(optionParts, 3)
|
||
contractSize = self.safe_number_2(market, 'contractSize', 'unit', self.parse_number('1'))
|
||
linear = settle == quote
|
||
inverse = settle == base
|
||
feesType = 'linear' if linear else 'inverse'
|
||
fees = self.safe_dict(self.fees, feesType, {})
|
||
active = (status == 'TRADING')
|
||
if spot:
|
||
permissions = self.safe_list(market, 'permissions', [])
|
||
for j in range(0, len(permissions)):
|
||
if permissions[j] == 'TRD_GRP_003':
|
||
active = False
|
||
break
|
||
isMarginTradingAllowed = self.safe_bool(market, 'isMarginTradingAllowed', False)
|
||
marginModes = None
|
||
if spot:
|
||
hasCrossMargin = self.in_array(id, self.options['crossMarginPairsData'])
|
||
hasIsolatedMargin = self.in_array(id, self.options['isolatedMarginPairsData'])
|
||
marginModes = {
|
||
'cross': hasCrossMargin,
|
||
'isolated': hasIsolatedMargin,
|
||
}
|
||
elif linear or inverse:
|
||
marginModes = {
|
||
'cross': True,
|
||
'isolated': True,
|
||
}
|
||
unifiedType = None
|
||
if spot:
|
||
unifiedType = 'spot'
|
||
elif swap:
|
||
unifiedType = 'swap'
|
||
elif future:
|
||
unifiedType = 'future'
|
||
elif option:
|
||
unifiedType = 'option'
|
||
active = None
|
||
parsedStrike = None
|
||
if strike is not None:
|
||
parsedStrike = self.parse_to_numeric(strike)
|
||
entry = {
|
||
'id': id,
|
||
'lowercaseId': lowercaseId,
|
||
'symbol': symbol,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': settle,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': settleId,
|
||
'type': unifiedType,
|
||
'spot': spot,
|
||
'margin': spot and isMarginTradingAllowed,
|
||
'marginModes': marginModes,
|
||
'swap': swap,
|
||
'future': future,
|
||
'option': option,
|
||
'active': active,
|
||
'contract': contract,
|
||
'linear': linear,
|
||
'inverse': inverse,
|
||
'taker': fees['trading']['taker'],
|
||
'maker': fees['trading']['maker'],
|
||
'contractSize': contractSize,
|
||
'expiry': expiry,
|
||
'expiryDatetime': self.iso8601(expiry),
|
||
'strike': parsedStrike,
|
||
'optionType': self.safe_string_lower(market, 'side'),
|
||
'precision': {
|
||
'amount': self.parse_number(self.parse_precision(self.safe_string_2(market, 'quantityPrecision', 'quantityScale'))),
|
||
'price': self.parse_number(self.parse_precision(self.safe_string_2(market, 'pricePrecision', 'priceScale'))),
|
||
'base': self.parse_number(self.parse_precision(self.safe_string(market, 'baseAssetPrecision'))),
|
||
'quote': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(market, 'minQty'),
|
||
'max': self.safe_number(market, 'maxQty'),
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'info': market,
|
||
'created': self.safe_integer(market, 'onboardDate'), # present in inverse & linear apis
|
||
}
|
||
if 'PRICE_FILTER' in filtersByType:
|
||
filter = self.safe_dict(filtersByType, 'PRICE_FILTER', {})
|
||
# PRICE_FILTER reports zero values for maxPrice
|
||
# since they updated filter types in November 2018
|
||
# https://github.com/ccxt/ccxt/issues/4286
|
||
# therefore limits['price']['max'] doesn't have any meaningful value except None
|
||
entry['limits']['price'] = {
|
||
'min': self.safe_number(filter, 'minPrice'),
|
||
'max': self.safe_number(filter, 'maxPrice'),
|
||
}
|
||
entry['precision']['price'] = self.safe_number(filter, 'tickSize')
|
||
if 'LOT_SIZE' in filtersByType:
|
||
filter = self.safe_dict(filtersByType, 'LOT_SIZE', {})
|
||
entry['precision']['amount'] = self.safe_number(filter, 'stepSize')
|
||
entry['limits']['amount'] = {
|
||
'min': self.safe_number(filter, 'minQty'),
|
||
'max': self.safe_number(filter, 'maxQty'),
|
||
}
|
||
if 'MARKET_LOT_SIZE' in filtersByType:
|
||
filter = self.safe_dict(filtersByType, 'MARKET_LOT_SIZE', {})
|
||
entry['limits']['market'] = {
|
||
'min': self.safe_number(filter, 'minQty'),
|
||
'max': self.safe_number(filter, 'maxQty'),
|
||
}
|
||
if ('MIN_NOTIONAL' in filtersByType) or ('NOTIONAL' in filtersByType): # notional added in 12/04/23 to spot testnet
|
||
filter = self.safe_dict_2(filtersByType, 'MIN_NOTIONAL', 'NOTIONAL', {})
|
||
entry['limits']['cost']['min'] = self.safe_number_2(filter, 'minNotional', 'notional')
|
||
entry['limits']['cost']['max'] = self.safe_number(filter, 'maxNotional')
|
||
return entry
|
||
|
||
def parse_balance_helper(self, entry):
|
||
account = self.account()
|
||
account['used'] = self.safe_string(entry, 'locked')
|
||
account['free'] = self.safe_string(entry, 'free')
|
||
interest = self.safe_string(entry, 'interest')
|
||
debt = self.safe_string(entry, 'borrowed')
|
||
account['debt'] = Precise.string_add(debt, interest)
|
||
return account
|
||
|
||
def parse_balance_custom(self, response, type=None, marginMode=None, isPortfolioMargin=False) -> Balances:
|
||
result = {
|
||
'info': response,
|
||
}
|
||
timestamp = None
|
||
isolated = marginMode == 'isolated'
|
||
cross = (type == 'margin') or (marginMode == 'cross')
|
||
if isPortfolioMargin:
|
||
for i in range(0, len(response)):
|
||
entry = response[i]
|
||
account = self.account()
|
||
currencyId = self.safe_string(entry, 'asset')
|
||
code = self.safe_currency_code(currencyId)
|
||
if type == 'linear':
|
||
account['free'] = self.safe_string(entry, 'umWalletBalance')
|
||
account['used'] = self.safe_string(entry, 'umUnrealizedPNL')
|
||
elif type == 'inverse':
|
||
account['free'] = self.safe_string(entry, 'cmWalletBalance')
|
||
account['used'] = self.safe_string(entry, 'cmUnrealizedPNL')
|
||
elif cross:
|
||
borrowed = self.safe_string(entry, 'crossMarginBorrowed')
|
||
interest = self.safe_string(entry, 'crossMarginInterest')
|
||
account['debt'] = Precise.string_add(borrowed, interest)
|
||
account['free'] = self.safe_string(entry, 'crossMarginFree')
|
||
account['used'] = self.safe_string(entry, 'crossMarginLocked')
|
||
account['total'] = self.safe_string(entry, 'crossMarginAsset')
|
||
else:
|
||
usedLinear = self.safe_string(entry, 'umUnrealizedPNL')
|
||
usedInverse = self.safe_string(entry, 'cmUnrealizedPNL')
|
||
totalUsed = Precise.string_add(usedLinear, usedInverse)
|
||
totalWalletBalance = self.safe_string(entry, 'totalWalletBalance')
|
||
account['total'] = Precise.string_add(totalUsed, totalWalletBalance)
|
||
result[code] = account
|
||
elif not isolated and ((type == 'spot') or cross):
|
||
timestamp = self.safe_integer(response, 'updateTime')
|
||
balances = self.safe_list_2(response, 'balances', 'userAssets', [])
|
||
for i in range(0, len(balances)):
|
||
balance = balances[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')
|
||
if cross:
|
||
debt = self.safe_string(balance, 'borrowed')
|
||
interest = self.safe_string(balance, 'interest')
|
||
account['debt'] = Precise.string_add(debt, interest)
|
||
result[code] = account
|
||
elif isolated:
|
||
assets = self.safe_list(response, 'assets')
|
||
for i in range(0, len(assets)):
|
||
asset = assets[i]
|
||
marketId = self.safe_string(asset, 'symbol')
|
||
symbol = self.safe_symbol(marketId, None, None, 'spot')
|
||
base = self.safe_dict(asset, 'baseAsset', {})
|
||
quote = self.safe_dict(asset, 'quoteAsset', {})
|
||
baseCode = self.safe_currency_code(self.safe_string(base, 'asset'))
|
||
quoteCode = self.safe_currency_code(self.safe_string(quote, 'asset'))
|
||
subResult: dict = {}
|
||
subResult[baseCode] = self.parse_balance_helper(base)
|
||
subResult[quoteCode] = self.parse_balance_helper(quote)
|
||
result[symbol] = self.safe_balance(subResult)
|
||
elif type == 'savings':
|
||
positionAmountVos = self.safe_list(response, 'positionAmountVos', [])
|
||
for i in range(0, len(positionAmountVos)):
|
||
entry = positionAmountVos[i]
|
||
currencyId = self.safe_string(entry, 'asset')
|
||
code = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
usedAndTotal = self.safe_string(entry, 'amount')
|
||
account['total'] = usedAndTotal
|
||
account['used'] = usedAndTotal
|
||
result[code] = account
|
||
elif type == 'funding':
|
||
for i in range(0, len(response)):
|
||
entry = response[i]
|
||
account = self.account()
|
||
currencyId = self.safe_string(entry, 'asset')
|
||
code = self.safe_currency_code(currencyId)
|
||
account['free'] = self.safe_string(entry, 'free')
|
||
frozen = self.safe_string(entry, 'freeze')
|
||
withdrawing = self.safe_string(entry, 'withdrawing')
|
||
locked = self.safe_string(entry, 'locked')
|
||
account['used'] = Precise.string_add(frozen, Precise.string_add(locked, withdrawing))
|
||
result[code] = account
|
||
else:
|
||
balances = response
|
||
if not isinstance(response, list):
|
||
balances = self.safe_list(response, 'assets', [])
|
||
for i in range(0, len(balances)):
|
||
balance = balances[i]
|
||
currencyId = self.safe_string(balance, 'asset')
|
||
code = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
account['free'] = self.safe_string(balance, 'availableBalance')
|
||
account['used'] = self.safe_string(balance, 'initialMargin')
|
||
account['total'] = self.safe_string_2(balance, 'marginBalance', 'balance')
|
||
result[code] = account
|
||
result['timestamp'] = timestamp
|
||
result['datetime'] = self.iso8601(timestamp)
|
||
return result if isolated else self.safe_balance(result)
|
||
|
||
def fetch_balance(self, params={}) -> Balances:
|
||
"""
|
||
query for balance and get the amount of funds available for trading or funds locked in orders
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/account-endpoints#account-information-user_data # spot
|
||
https://developers.binance.com/docs/margin_trading/account/Query-Cross-Margin-Account-Details # cross margin
|
||
https://developers.binance.com/docs/margin_trading/account/Query-Isolated-Margin-Account-Info # isolated margin
|
||
https://developers.binance.com/docs/wallet/asset/funding-wallet # funding
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Futures-Account-Balance-V2 # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Futures-Account-Balance # future
|
||
https://developers.binance.com/docs/derivatives/option/account/Option-Account-Information # option
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Account-Balance # portfolio margin
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: 'future', 'delivery', 'savings', 'funding', or 'spot' or 'papi'
|
||
:param str [params.marginMode]: 'cross' or 'isolated', for margin trading, uses self.options.defaultMarginMode if not passed, defaults to None/None/None
|
||
:param str[]|None [params.symbols]: unified market symbols, only used in isolated margin mode
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch the balance for a portfolio margin account
|
||
:param str [params.subType]: 'linear' or 'inverse'
|
||
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
||
"""
|
||
self.load_markets()
|
||
defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchBalance', None, params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchBalance', 'papi', 'portfolioMargin', False)
|
||
marginMode = None
|
||
query = None
|
||
marginMode, query = self.handle_margin_mode_and_params('fetchBalance', params)
|
||
query = self.omit(query, 'type')
|
||
response = None
|
||
request: dict = {}
|
||
if isPortfolioMargin or (type == 'papi'):
|
||
if self.is_linear(type, subType):
|
||
type = 'linear'
|
||
elif self.is_inverse(type, subType):
|
||
type = 'inverse'
|
||
isPortfolioMargin = True
|
||
response = self.papiGetBalance(self.extend(request, query))
|
||
elif self.is_linear(type, subType):
|
||
type = 'linear'
|
||
useV2 = None
|
||
useV2, params = self.handle_option_and_params(params, 'fetchBalance', 'useV2', False)
|
||
params = self.extend(request, query)
|
||
if not useV2:
|
||
response = self.fapiPrivateV3GetAccount(params)
|
||
else:
|
||
response = self.fapiPrivateV2GetAccount(params)
|
||
elif self.is_inverse(type, subType):
|
||
type = 'inverse'
|
||
response = self.dapiPrivateGetAccount(self.extend(request, query))
|
||
elif marginMode == 'isolated':
|
||
paramSymbols = self.safe_list(params, 'symbols')
|
||
query = self.omit(query, 'symbols')
|
||
if paramSymbols is not None:
|
||
symbols = ''
|
||
if isinstance(paramSymbols, list):
|
||
symbols = self.market_id(paramSymbols[0])
|
||
for i in range(1, len(paramSymbols)):
|
||
symbol = paramSymbols[i]
|
||
id = self.market_id(symbol)
|
||
symbols += ',' + id
|
||
else:
|
||
symbols = paramSymbols
|
||
request['symbols'] = symbols
|
||
response = self.sapiGetMarginIsolatedAccount(self.extend(request, query))
|
||
elif (type == 'margin') or (marginMode == 'cross'):
|
||
response = self.sapiGetMarginAccount(self.extend(request, query))
|
||
elif type == 'savings':
|
||
response = self.sapiGetLendingUnionAccount(self.extend(request, query))
|
||
elif type == 'funding':
|
||
response = self.sapiPostAssetGetFundingAsset(self.extend(request, query))
|
||
else:
|
||
response = self.privateGetAccount(self.extend(request, query))
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "makerCommission": 10,
|
||
# "takerCommission": 10,
|
||
# "buyerCommission": 0,
|
||
# "sellerCommission": 0,
|
||
# "canTrade": True,
|
||
# "canWithdraw": True,
|
||
# "canDeposit": True,
|
||
# "updateTime": 1575357359602,
|
||
# "accountType": "MARGIN",
|
||
# "balances": [
|
||
# {asset: "BTC", free: "0.00219821", locked: "0.00000000" },
|
||
# ]
|
||
# }
|
||
#
|
||
# margin(cross)
|
||
#
|
||
# {
|
||
# "borrowEnabled":true,
|
||
# "marginLevel":"999.00000000",
|
||
# "totalAssetOfBtc":"0.00000000",
|
||
# "totalLiabilityOfBtc":"0.00000000",
|
||
# "totalNetAssetOfBtc":"0.00000000",
|
||
# "tradeEnabled":true,
|
||
# "transferEnabled":true,
|
||
# "userAssets":[
|
||
# {"asset":"MATIC","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"},
|
||
# {"asset":"VET","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"},
|
||
# {"asset":"USDT","borrowed":"0.00000000","free":"0.00000000","interest":"0.00000000","locked":"0.00000000","netAsset":"0.00000000"}
|
||
# ],
|
||
# }
|
||
#
|
||
# margin(isolated)
|
||
#
|
||
# {
|
||
# "info": {
|
||
# "assets": [
|
||
# {
|
||
# "baseAsset": {
|
||
# "asset": "1INCH",
|
||
# "borrowEnabled": True,
|
||
# "borrowed": "0",
|
||
# "free": "0",
|
||
# "interest": "0",
|
||
# "locked": "0",
|
||
# "netAsset": "0",
|
||
# "netAssetOfBtc": "0",
|
||
# "repayEnabled": True,
|
||
# "totalAsset": "0"
|
||
# },
|
||
# "quoteAsset": {
|
||
# "asset": "USDT",
|
||
# "borrowEnabled": True,
|
||
# "borrowed": "0",
|
||
# "free": "11",
|
||
# "interest": "0",
|
||
# "locked": "0",
|
||
# "netAsset": "11",
|
||
# "netAssetOfBtc": "0.00054615",
|
||
# "repayEnabled": True,
|
||
# "totalAsset": "11"
|
||
# },
|
||
# "symbol": "1INCHUSDT",
|
||
# "isolatedCreated": True,
|
||
# "marginLevel": "999",
|
||
# "marginLevelStatus": "EXCESSIVE",
|
||
# "marginRatio": "5",
|
||
# "indexPrice": "0.59184331",
|
||
# "liquidatePrice": "0",
|
||
# "liquidateRate": "0",
|
||
# "tradeEnabled": True,
|
||
# "enabled": True
|
||
# },
|
||
# ]
|
||
# }
|
||
# }
|
||
#
|
||
# futures(fapi)
|
||
#
|
||
# fapiPrivateV3GetAccount
|
||
#
|
||
# {
|
||
# "feeTier":0,
|
||
# "canTrade":true,
|
||
# "canDeposit":true,
|
||
# "canWithdraw":true,
|
||
# "updateTime":0,
|
||
# "totalInitialMargin":"0.00000000",
|
||
# "totalMaintMargin":"0.00000000",
|
||
# "totalWalletBalance":"0.00000000",
|
||
# "totalUnrealizedProfit":"0.00000000",
|
||
# "totalMarginBalance":"0.00000000",
|
||
# "totalPositionInitialMargin":"0.00000000",
|
||
# "totalOpenOrderInitialMargin":"0.00000000",
|
||
# "totalCrossWalletBalance":"0.00000000",
|
||
# "totalCrossUnPnl":"0.00000000",
|
||
# "availableBalance":"0.00000000",
|
||
# "maxWithdrawAmount":"0.00000000",
|
||
# "assets":[
|
||
# {
|
||
# "asset":"BNB",
|
||
# "walletBalance":"0.01000000",
|
||
# "unrealizedProfit":"0.00000000",
|
||
# "marginBalance":"0.01000000",
|
||
# "maintMargin":"0.00000000",
|
||
# "initialMargin":"0.00000000",
|
||
# "positionInitialMargin":"0.00000000",
|
||
# "openOrderInitialMargin":"0.00000000",
|
||
# "maxWithdrawAmount":"0.01000000",
|
||
# "crossWalletBalance":"0.01000000",
|
||
# "crossUnPnl":"0.00000000",
|
||
# "availableBalance":"0.01000000"
|
||
# }
|
||
# ],
|
||
# "positions":[
|
||
# {
|
||
# "symbol":"BTCUSDT",
|
||
# "initialMargin":"0",
|
||
# "maintMargin":"0",
|
||
# "unrealizedProfit":"0.00000000",
|
||
# "positionInitialMargin":"0",
|
||
# "openOrderInitialMargin":"0",
|
||
# "leverage":"21",
|
||
# "isolated":false,
|
||
# "entryPrice":"0.00000",
|
||
# "maxNotional":"5000000",
|
||
# "positionSide":"BOTH"
|
||
# },
|
||
# ]
|
||
# }
|
||
#
|
||
# fapiPrivateV2GetBalance
|
||
#
|
||
# [
|
||
# {
|
||
# "accountAlias":"FzFzXquXXqoC",
|
||
# "asset":"BNB",
|
||
# "balance":"0.01000000",
|
||
# "crossWalletBalance":"0.01000000",
|
||
# "crossUnPnl":"0.00000000",
|
||
# "availableBalance":"0.01000000",
|
||
# "maxWithdrawAmount":"0.01000000"
|
||
# }
|
||
# ]
|
||
#
|
||
# binance pay
|
||
#
|
||
# [
|
||
# {
|
||
# "asset": "BUSD",
|
||
# "free": "1129.83",
|
||
# "locked": "0",
|
||
# "freeze": "0",
|
||
# "withdrawing": "0"
|
||
# }
|
||
# ]
|
||
#
|
||
# portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "asset": "USDT",
|
||
# "totalWalletBalance": "66.9923261",
|
||
# "crossMarginAsset": "35.9697141",
|
||
# "crossMarginBorrowed": "0.0",
|
||
# "crossMarginFree": "35.9697141",
|
||
# "crossMarginInterest": "0.0",
|
||
# "crossMarginLocked": "0.0",
|
||
# "umWalletBalance": "31.022612",
|
||
# "umUnrealizedPNL": "0.0",
|
||
# "cmWalletBalance": "0.0",
|
||
# "cmUnrealizedPNL": "0.0",
|
||
# "updateTime": 0,
|
||
# "negativeBalance": "0.0"
|
||
# },
|
||
# ]
|
||
#
|
||
return self.parse_balance_custom(response, type, marginMode, isPortfolioMargin)
|
||
|
||
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://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#order-book # spot
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Order-Book # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Order-Book # future
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Order-Book # option
|
||
|
||
: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 # default 100, max 5000, see https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#order-book
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPublicGetDepth(self.extend(request, params))
|
||
elif market['linear']:
|
||
response = self.fapiPublicGetDepth(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPublicGetDepth(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetDepth(self.extend(request, params))
|
||
#
|
||
# future
|
||
#
|
||
# {
|
||
# "lastUpdateId":333598053905,
|
||
# "E":1618631511986,
|
||
# "T":1618631511964,
|
||
# "bids":[
|
||
# ["2493.56","20.189"],
|
||
# ["2493.54","1.000"],
|
||
# ["2493.51","0.005"]
|
||
# ],
|
||
# "asks":[
|
||
# ["2493.57","0.877"],
|
||
# ["2493.62","0.063"],
|
||
# ["2493.71","12.054"],
|
||
# ]
|
||
# }
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# {
|
||
# "bids": [
|
||
# ["108.7","16.08"],
|
||
# ["106","21.29"],
|
||
# ["82.4","0.02"]
|
||
# ],
|
||
# "asks": [
|
||
# ["111.4","19.52"],
|
||
# ["119.9","17.6"],
|
||
# ["141.2","31"]
|
||
# ],
|
||
# "T": 1676771382078,
|
||
# "u": 1015939
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(response, 'T')
|
||
orderbook = self.parse_order_book(response, symbol, timestamp)
|
||
orderbook['nonce'] = self.safe_integer_2(response, 'lastUpdateId', 'u')
|
||
return orderbook
|
||
|
||
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
||
# markPrices
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "markPrice": "11793.63104561", # mark price
|
||
# "indexPrice": "11781.80495970", # index price
|
||
# "estimatedSettlePrice": "11781.16138815", # Estimated Settle Price, only useful in the last hour before the settlement starts
|
||
# "lastFundingRate": "0.00038246", # This is the lastest estimated funding rate
|
||
# "nextFundingTime": 1597392000000,
|
||
# "interestRate": "0.00010000",
|
||
# "time": 1597370495002
|
||
# }
|
||
#
|
||
# spot - ticker
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "priceChange": "-188.18000000",
|
||
# "priceChangePercent": "-0.159",
|
||
# "weightedAvgPrice": "118356.64734074",
|
||
# "lastPrice": "118449.03000000",
|
||
# "prevClosePrice": "118637.22000000", # field absent in rolling ticker
|
||
# "lastQty": "0.00731000", # field absent in rolling ticker
|
||
# "bidPrice": "118449.02000000", # field absent in rolling ticker
|
||
# "bidQty": "7.15931000", # field absent in rolling ticker
|
||
# "askPrice": "118449.03000000", # field absent in rolling ticker
|
||
# "askQty": "0.09592000", # field absent in rolling ticker
|
||
# "openPrice": "118637.21000000",
|
||
# "highPrice": "119273.36000000",
|
||
# "lowPrice": "117427.50000000",
|
||
# "volume": "14741.41491000",
|
||
# "quoteVolume": "1744744445.80640740",
|
||
# "openTime": "1753701474013",
|
||
# "closeTime": "1753787874013",
|
||
# "firstId": "5116031635",
|
||
# "lastId": "5117964946",
|
||
# "count": "1933312"
|
||
# }
|
||
#
|
||
# usdm tickers
|
||
#
|
||
# {
|
||
# "symbol": "SUSDT",
|
||
# "priceChange": "-0.0229000",
|
||
# "priceChangePercent": "-6.777",
|
||
# "weightedAvgPrice": "0.3210035",
|
||
# "lastPrice": "0.3150000",
|
||
# "lastQty": "16",
|
||
# "openPrice": "0.3379000",
|
||
# "highPrice": "0.3411000",
|
||
# "lowPrice": "0.3071000",
|
||
# "volume": "120588225",
|
||
# "quoteVolume": "38709237.2289000",
|
||
# "openTime": "1753701720000",
|
||
# "closeTime": "1753788172414",
|
||
# "firstId": "72234973",
|
||
# "lastId": "72423677",
|
||
# "count": "188700"
|
||
# }
|
||
#
|
||
# coinm
|
||
#
|
||
# {
|
||
# "baseVolume": "214549.95171161",
|
||
# "closeTime": "1621965286847",
|
||
# "count": "1283779",
|
||
# "firstId": "152560106",
|
||
# "highPrice": "39938.3",
|
||
# "lastId": "153843955",
|
||
# "lastPrice": "37993.4",
|
||
# "lastQty": "1",
|
||
# "lowPrice": "36457.2",
|
||
# "openPrice": "37783.4",
|
||
# "openTime": "1621878840000",
|
||
# "pair": "BTCUSD",
|
||
# "priceChange": "210.0",
|
||
# "priceChangePercent": "0.556",
|
||
# "symbol": "BTCUSD_PERP",
|
||
# "volume": "81990451",
|
||
# "weightedAvgPrice": "38215.08713747"
|
||
# }
|
||
#
|
||
# eapi: fetchTicker, fetchTickers
|
||
#
|
||
# {
|
||
# "symbol": "ETH-230510-1825-C",
|
||
# "priceChange": "-5.1",
|
||
# "priceChangePercent": "-0.1854",
|
||
# "lastPrice": "22.4",
|
||
# "lastQty": "0",
|
||
# "open": "27.5",
|
||
# "high": "34.1",
|
||
# "low": "22.4",
|
||
# "volume": "6.83",
|
||
# "amount": "201.44",
|
||
# "bidPrice": "21.9",
|
||
# "askPrice": "22.4",
|
||
# "openTime": 1683614771898,
|
||
# "closeTime": 1683695017784,
|
||
# "firstTradeId": 12,
|
||
# "tradeCount": 22,
|
||
# "strikePrice": "1825",
|
||
# "exercisePrice": "1845.95341176"
|
||
# }
|
||
#
|
||
# spot bidsAsks
|
||
#
|
||
# {
|
||
# "symbol":"ETHBTC",
|
||
# "bidPrice":"0.07466800",
|
||
# "bidQty":"5.31990000",
|
||
# "askPrice":"0.07466900",
|
||
# "askQty":"10.93540000"
|
||
# }
|
||
#
|
||
# usdm bidsAsks
|
||
#
|
||
# {
|
||
# "symbol":"BTCUSDT",
|
||
# "bidPrice":"21321.90",
|
||
# "bidQty":"33.592",
|
||
# "askPrice":"21322.00",
|
||
# "askQty":"1.427",
|
||
# "time":"1673899207538"
|
||
# }
|
||
#
|
||
# coinm bidsAsks
|
||
#
|
||
# {
|
||
# "symbol":"BTCUSD_PERP",
|
||
# "pair":"BTCUSD",
|
||
# "bidPrice":"21301.2",
|
||
# "bidQty":"188",
|
||
# "askPrice":"21301.3",
|
||
# "askQty":"10302",
|
||
# "time":"1673899278514"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer_2(ticker, 'closeTime', 'time')
|
||
marketType = None
|
||
if ('time' in ticker):
|
||
marketType = 'contract'
|
||
if marketType is None:
|
||
marketType = 'spot' if ('bidQty' in ticker) else 'contract'
|
||
marketId = self.safe_string(ticker, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market, None, marketType)
|
||
last = self.safe_string(ticker, 'lastPrice')
|
||
wAvg = self.safe_string(ticker, 'weightedAvgPrice')
|
||
isCoinm = ('baseVolume' in ticker)
|
||
baseVolume = None
|
||
quoteVolume = None
|
||
if isCoinm:
|
||
baseVolume = self.safe_string(ticker, 'baseVolume')
|
||
# 'volume' field in inverse markets is not quoteVolume, but traded amount(per contracts)
|
||
quoteVolume = Precise.string_mul(baseVolume, wAvg)
|
||
else:
|
||
baseVolume = self.safe_string(ticker, 'volume')
|
||
quoteVolume = self.safe_string_2(ticker, 'quoteVolume', 'amount')
|
||
return self.safe_ticker({
|
||
'symbol': symbol,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'high': self.safe_string_2(ticker, 'highPrice', 'high'),
|
||
'low': self.safe_string_2(ticker, 'lowPrice', 'low'),
|
||
'bid': self.safe_string(ticker, 'bidPrice'),
|
||
'bidVolume': self.safe_string(ticker, 'bidQty'),
|
||
'ask': self.safe_string(ticker, 'askPrice'),
|
||
'askVolume': self.safe_string(ticker, 'askQty'),
|
||
'vwap': wAvg,
|
||
'open': self.safe_string_2(ticker, 'openPrice', 'open'),
|
||
'close': last,
|
||
'last': last,
|
||
'previousClose': self.safe_string(ticker, 'prevClosePrice'), # previous day close
|
||
'change': self.safe_string(ticker, 'priceChange'),
|
||
'percentage': self.safe_string(ticker, 'priceChangePercent'),
|
||
'average': None,
|
||
'baseVolume': baseVolume,
|
||
'quoteVolume': quoteVolume,
|
||
'markPrice': self.safe_string(ticker, 'markPrice'),
|
||
'indexPrice': self.safe_string(ticker, 'indexPrice'),
|
||
'info': ticker,
|
||
}, market)
|
||
|
||
def fetch_status(self, params={}):
|
||
"""
|
||
the latest known information on the availability of the exchange API
|
||
|
||
https://developers.binance.com/docs/wallet/others/system-status
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
|
||
"""
|
||
response = self.sapiGetSystemStatus(params)
|
||
#
|
||
# {
|
||
# "status": 0, # 0: normal,1:system maintenance
|
||
# "msg": "normal" # "normal", "system_maintenance"
|
||
# }
|
||
#
|
||
statusRaw = self.safe_string(response, 'status')
|
||
return {
|
||
'status': self.safe_string({'0': 'ok', '1': 'maintenance'}, statusRaw, statusRaw),
|
||
'updated': None,
|
||
'eta': None,
|
||
'url': None,
|
||
'info': response,
|
||
}
|
||
|
||
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://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#24hr-ticker-price-change-statistics # spot
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#rolling-window-price-change-statistics # spot
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/24hr-Ticker-Price-Change-Statistics # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/24hr-Ticker-Price-Change-Statistics # future
|
||
https://developers.binance.com/docs/derivatives/option/market-data/24hr-Ticker-Price-Change-Statistics # option
|
||
|
||
:param str symbol: unified symbol of the market to fetch the ticker for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.rolling]:(spot only) default False, if True, uses the rolling 24 hour ticker endpoint /api/v3/ticker
|
||
: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['option']:
|
||
response = self.eapiPublicGetTicker(self.extend(request, params))
|
||
elif market['linear']:
|
||
response = self.fapiPublicGetTicker24hr(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPublicGetTicker24hr(self.extend(request, params))
|
||
else:
|
||
rolling = self.safe_bool(params, 'rolling', False)
|
||
params = self.omit(params, 'rolling')
|
||
if rolling:
|
||
response = self.publicGetTicker(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetTicker24hr(self.extend(request, params))
|
||
if isinstance(response, list):
|
||
firstTicker = self.safe_dict(response, 0, {})
|
||
return self.parse_ticker(firstTicker, market)
|
||
return self.parse_ticker(response, market)
|
||
|
||
def fetch_bids_asks(self, symbols: Strings = None, params={}):
|
||
"""
|
||
fetches the bid and ask price and volume for multiple markets
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#symbol-order-book-ticker # spot
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Symbol-Order-Book-Ticker # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Symbol-Order-Book-Ticker # future
|
||
|
||
:param str[]|None symbols: unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols, None, True, True, True)
|
||
market = self.get_market_from_symbols(symbols)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchBidsAsks', market, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchBidsAsks', market, params)
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicGetTickerBookTicker(params)
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetTickerBookTicker(params)
|
||
elif type == 'spot':
|
||
request: dict = {}
|
||
if symbols is not None:
|
||
request['symbols'] = self.json(self.market_ids(symbols))
|
||
response = self.publicGetTickerBookTicker(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchBidsAsks() does not support ' + type + ' markets yet')
|
||
return self.parse_tickers(response, symbols)
|
||
|
||
def fetch_last_prices(self, symbols: Strings = None, params={}):
|
||
"""
|
||
fetches the last price for multiple markets
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#symbol-price-ticker # spot
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Symbol-Price-Ticker # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Symbol-Price-Ticker # future
|
||
|
||
:param str[]|None symbols: unified symbols of the markets to fetch the last prices
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a dictionary of lastprices structures
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols, None, True, True, True)
|
||
market = self.get_market_from_symbols(symbols)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchLastPrices', market, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchLastPrices', market, params)
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicV2GetTickerPrice(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "LTCBTC",
|
||
# "price": "4.00000200"
|
||
# "time": 1589437530011
|
||
# },
|
||
# ...
|
||
# ]
|
||
#
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetTickerPrice(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSD_200626",
|
||
# "ps": "9647.8",
|
||
# "price": "9647.8",
|
||
# "time": 1591257246176
|
||
# }
|
||
# ]
|
||
#
|
||
elif type == 'spot':
|
||
response = self.publicGetTickerPrice(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "LTCBTC",
|
||
# "price": "4.00000200"
|
||
# },
|
||
# ...
|
||
# ]
|
||
#
|
||
else:
|
||
raise NotSupported(self.id + ' fetchLastPrices() does not support ' + type + ' markets yet')
|
||
return self.parse_last_prices(response, symbols)
|
||
|
||
def parse_last_price(self, entry, market: Market = None):
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "symbol": "LTCBTC",
|
||
# "price": "4.00000200"
|
||
# }
|
||
#
|
||
# usdm(swap/future)
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "price": "6000.01",
|
||
# "time": 1589437530011 # Transaction time
|
||
# }
|
||
#
|
||
#
|
||
# coinm(swap/future)
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSD_200626", # symbol("BTCUSD_200626", "BTCUSD_PERP", etc..)
|
||
# "ps": "BTCUSD", # pair
|
||
# "price": "9647.8",
|
||
# "time": 1591257246176
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(entry, 'time')
|
||
type = 'spot' if (timestamp is None) else 'swap'
|
||
marketId = self.safe_string(entry, 'symbol')
|
||
market = self.safe_market(marketId, market, None, type)
|
||
return {
|
||
'symbol': market['symbol'],
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'price': self.safe_number_omit_zero(entry, 'price'),
|
||
'side': None,
|
||
'info': entry,
|
||
}
|
||
|
||
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://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#24hr-ticker-price-change-statistics # spot
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/24hr-Ticker-Price-Change-Statistics # swap
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/24hr-Ticker-Price-Change-Statistics # future
|
||
https://developers.binance.com/docs/derivatives/option/market-data/24hr-Ticker-Price-Change-Statistics # option
|
||
|
||
: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
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:param str [params.type]: 'spot', 'option', use params["subType"] for swap and future markets
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols, None, True, True, True)
|
||
market = self.get_market_from_symbols(symbols)
|
||
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 self.is_linear(type, subType):
|
||
response = self.fapiPublicGetTicker24hr(params)
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetTicker24hr(params)
|
||
elif type == 'spot':
|
||
rolling = self.safe_bool(params, 'rolling', False)
|
||
params = self.omit(params, 'rolling')
|
||
if rolling:
|
||
symbols = self.market_symbols(symbols)
|
||
request: dict = {
|
||
'symbols': self.json(self.market_ids(symbols)),
|
||
}
|
||
response = self.publicGetTicker(self.extend(request, params))
|
||
# parseTicker is not able to handle marketType for spot-rolling ticker fields, so we need custom parsing
|
||
return self.parse_tickers_for_rolling(response, symbols)
|
||
else:
|
||
request: dict = {}
|
||
if symbols is not None:
|
||
request['symbols'] = self.json(self.market_ids(symbols))
|
||
response = self.publicGetTicker24hr(self.extend(request, params))
|
||
elif type == 'option':
|
||
response = self.eapiPublicGetTicker(params)
|
||
else:
|
||
raise NotSupported(self.id + ' fetchTickers() does not support ' + type + ' markets yet')
|
||
return self.parse_tickers(response, symbols)
|
||
|
||
def parse_tickers_for_rolling(self, response, symbols):
|
||
results = []
|
||
for i in range(0, len(response)):
|
||
marketId = self.safe_string(response[i], 'symbol')
|
||
tickerMarket = self.safe_market(marketId, None, None, 'spot')
|
||
parsedTicker = self.parse_ticker(response[i])
|
||
parsedTicker['symbol'] = tickerMarket['symbol']
|
||
results.append(parsedTicker)
|
||
return self.filter_by_array(results, 'symbol', symbols)
|
||
|
||
def fetch_mark_price(self, symbol: str, params={}) -> Ticker:
|
||
"""
|
||
fetches mark price for the market
|
||
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Index-Price-and-Mark-Price
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Mark-Price
|
||
|
||
:param str symbol: unified symbol of the market to fetch the ticker for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchMarkPrice', market, params, 'swap')
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchMarkPrice', market, params, 'linear')
|
||
request = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicGetPremiumIndex(self.extend(request, params))
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetPremiumIndex(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchMarkPrice() does not support ' + type + ' markets yet')
|
||
if isinstance(response, list):
|
||
return self.parse_ticker(self.safe_dict(response, 0, {}), market)
|
||
return self.parse_ticker(response, market)
|
||
|
||
def fetch_mark_prices(self, symbols: Strings = None, params={}) -> Tickers:
|
||
"""
|
||
fetches mark prices for multiple markets
|
||
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Index-Price-and-Mark-Price
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Mark-Price
|
||
|
||
: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
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols, None, True, True, True)
|
||
market = self.get_market_from_symbols(symbols)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchMarkPrices', market, params, 'swap')
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchMarkPrices', market, params, 'linear')
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicGetPremiumIndex(params)
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetPremiumIndex(params)
|
||
else:
|
||
raise NotSupported(self.id + ' fetchMarkPrices() does not support ' + type + ' markets yet')
|
||
return self.parse_tickers(response, symbols)
|
||
|
||
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
||
# when api method = publicGetKlines or fapiPublicGetKlines or dapiPublicGetKlines
|
||
# [
|
||
# 1591478520000, # open time
|
||
# "0.02501300", # open
|
||
# "0.02501800", # high
|
||
# "0.02500000", # low
|
||
# "0.02500000", # close
|
||
# "22.19000000", # volume
|
||
# 1591478579999, # close time
|
||
# "0.55490906", # quote asset volume, base asset volume for dapi
|
||
# 40, # number of trades
|
||
# "10.92900000", # taker buy base asset volume
|
||
# "0.27336462", # taker buy quote asset volume
|
||
# "0" # ignore
|
||
# ]
|
||
#
|
||
# when api method = fapiPublicGetMarkPriceKlines or fapiPublicGetIndexPriceKlines
|
||
# [
|
||
# [
|
||
# 1591256460000, # Open time
|
||
# "9653.29201333", # Open
|
||
# "9654.56401333", # High
|
||
# "9653.07367333", # Low
|
||
# "9653.07367333", # Close(or latest price)
|
||
# "0", # Ignore
|
||
# 1591256519999, # Close time
|
||
# "0", # Ignore
|
||
# 60, # Number of bisic data
|
||
# "0", # Ignore
|
||
# "0", # Ignore
|
||
# "0" # Ignore
|
||
# ]
|
||
# ]
|
||
#
|
||
# options
|
||
#
|
||
# {
|
||
# "open": "32.2",
|
||
# "high": "32.2",
|
||
# "low": "32.2",
|
||
# "close": "32.2",
|
||
# "volume": "0",
|
||
# "interval": "5m",
|
||
# "tradeCount": 0,
|
||
# "takerVolume": "0",
|
||
# "takerAmount": "0",
|
||
# "amount": "0",
|
||
# "openTime": 1677096900000,
|
||
# "closeTime": 1677097200000
|
||
# }
|
||
#
|
||
inverse = self.safe_bool(market, 'inverse')
|
||
volumeIndex = 7 if inverse else 5
|
||
return [
|
||
self.safe_integer_2(ohlcv, 0, 'openTime'),
|
||
self.safe_number_2(ohlcv, 1, 'open'),
|
||
self.safe_number_2(ohlcv, 2, 'high'),
|
||
self.safe_number_2(ohlcv, 3, 'low'),
|
||
self.safe_number_2(ohlcv, 4, 'close'),
|
||
self.safe_number_2(ohlcv, volumeIndex, 'volume'),
|
||
]
|
||
|
||
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://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#klinecandlestick-data
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Kline-Candlestick-Data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Kline-Candlestick-Data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Index-Price-Kline-Candlestick-Data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Mark-Price-Kline-Candlestick-Data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Premium-Index-Kline-Data
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Kline-Candlestick-Data
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Index-Price-Kline-Candlestick-Data
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Mark-Price-Kline-Candlestick-Data
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Premium-Index-Kline-Data
|
||
|
||
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
||
:param str timeframe: the length of time each candle represents
|
||
:param int [since]: timestamp in ms of the earliest candle to fetch
|
||
:param int [limit]: the maximum amount of candles to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.price]: "mark" or "index" for mark price and index price candles
|
||
: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, 1000)
|
||
market = self.market(symbol)
|
||
# binance docs say that the default limit 500, max 1500 for futures, max 1000 for spot markets
|
||
# the reality is that the time range wider than 500 candles won't work right
|
||
defaultLimit = 500
|
||
maxLimit = 1500
|
||
price = self.safe_string(params, 'price')
|
||
until = self.safe_integer(params, 'until')
|
||
params = self.omit(params, ['price', 'until'])
|
||
if since is not None and until is not None and limit is None:
|
||
limit = maxLimit
|
||
limit = defaultLimit if (limit is None) else min(limit, maxLimit)
|
||
request: dict = {
|
||
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
|
||
'limit': limit,
|
||
}
|
||
marketId = market['id']
|
||
if price == 'index':
|
||
parts = marketId.split('_')
|
||
pair = self.safe_string(parts, 0)
|
||
request['pair'] = pair # Index price takes self argument instead of symbol
|
||
else:
|
||
request['symbol'] = marketId
|
||
# duration = self.parse_timeframe(timeframe)
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
#
|
||
# It didn't work before without the endTime
|
||
# https://github.com/ccxt/ccxt/issues/8454
|
||
#
|
||
if market['inverse']:
|
||
if since > 0:
|
||
duration = self.parse_timeframe(timeframe)
|
||
endTime = self.sum(since, limit * duration * 1000 - 1)
|
||
now = self.milliseconds()
|
||
request['endTime'] = min(now, endTime)
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPublicGetKlines(self.extend(request, params))
|
||
elif price == 'mark':
|
||
if market['inverse']:
|
||
response = self.dapiPublicGetMarkPriceKlines(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPublicGetMarkPriceKlines(self.extend(request, params))
|
||
elif price == 'index':
|
||
if market['inverse']:
|
||
response = self.dapiPublicGetIndexPriceKlines(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPublicGetIndexPriceKlines(self.extend(request, params))
|
||
elif price == 'premiumIndex':
|
||
if market['inverse']:
|
||
response = self.dapiPublicGetPremiumIndexKlines(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPublicGetPremiumIndexKlines(self.extend(request, params))
|
||
elif market['linear']:
|
||
response = self.fapiPublicGetKlines(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPublicGetKlines(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetKlines(self.extend(request, params))
|
||
#
|
||
# [
|
||
# [1591478520000,"0.02501300","0.02501800","0.02500000","0.02500000","22.19000000",1591478579999,"0.55490906",40,"10.92900000","0.27336462","0"],
|
||
# [1591478580000,"0.02499600","0.02500900","0.02499400","0.02500300","21.34700000",1591478639999,"0.53370468",24,"7.53800000","0.18850725","0"],
|
||
# [1591478640000,"0.02500800","0.02501100","0.02500300","0.02500800","154.14200000",1591478699999,"3.85405839",97,"5.32300000","0.13312641","0"],
|
||
# ]
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# [
|
||
# {
|
||
# "open": "32.2",
|
||
# "high": "32.2",
|
||
# "low": "32.2",
|
||
# "close": "32.2",
|
||
# "volume": "0",
|
||
# "interval": "5m",
|
||
# "tradeCount": 0,
|
||
# "takerVolume": "0",
|
||
# "takerAmount": "0",
|
||
# "amount": "0",
|
||
# "openTime": 1677096900000,
|
||
# "closeTime": 1677097200000
|
||
# }
|
||
# ]
|
||
#
|
||
candles = self.parse_ohlcvs(response, market, timeframe, since, limit)
|
||
return candles
|
||
|
||
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
||
if 'isDustTrade' in trade:
|
||
return self.parse_dust_trade(trade, market)
|
||
#
|
||
# aggregate trades
|
||
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
|
||
#
|
||
# {
|
||
# "a": 26129, # Aggregate tradeId
|
||
# "p": "0.01633102", # Price
|
||
# "q": "4.70443515", # Quantity
|
||
# "f": 27781, # First tradeId
|
||
# "l": 27781, # Last tradeId
|
||
# "T": 1498793709153, # Timestamp
|
||
# "m": True, # Was the buyer the maker?
|
||
# "M": True # Was the trade the best price match?
|
||
# }
|
||
#
|
||
# REST: aggregate trades for swap & future(both linear and inverse)
|
||
#
|
||
# {
|
||
# "a": "269772814",
|
||
# "p": "25864.1",
|
||
# "q": "3",
|
||
# "f": "662149354",
|
||
# "l": "662149355",
|
||
# "T": "1694209776022",
|
||
# "m": False,
|
||
# }
|
||
#
|
||
# recent public trades and old public trades
|
||
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list
|
||
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data
|
||
#
|
||
# {
|
||
# "id": 28457,
|
||
# "price": "4.00000100",
|
||
# "qty": "12.00000000",
|
||
# "time": 1499865549590,
|
||
# "isBuyerMaker": True,
|
||
# "isBestMatch": True
|
||
# }
|
||
#
|
||
# private trades
|
||
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data
|
||
#
|
||
# {
|
||
# "symbol": "BNBBTC",
|
||
# "id": 28457,
|
||
# "orderId": 100234,
|
||
# "price": "4.00000100",
|
||
# "qty": "12.00000000",
|
||
# "commission": "10.10000000",
|
||
# "commissionAsset": "BNB",
|
||
# "time": 1499865549590,
|
||
# "isBuyer": True,
|
||
# "isMaker": False,
|
||
# "isBestMatch": True
|
||
# }
|
||
#
|
||
# futures trades
|
||
#
|
||
# {
|
||
# "accountId": 20,
|
||
# "buyer": False,
|
||
# "commission": "-0.07819010",
|
||
# "commissionAsset": "USDT",
|
||
# "counterPartyId": 653,
|
||
# "id": 698759,
|
||
# "maker": False,
|
||
# "orderId": 25851813,
|
||
# "price": "7819.01",
|
||
# "qty": "0.002",
|
||
# "quoteQty": "0.01563",
|
||
# "realizedPnl": "-0.91539999",
|
||
# "side": "SELL",
|
||
# "symbol": "BTCUSDT",
|
||
# "time": 1569514978020
|
||
# }
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "id": 477128891,
|
||
# "orderId": 13809777875,
|
||
# "side": "SELL",
|
||
# "price": "38479.55",
|
||
# "qty": "0.001",
|
||
# "realizedPnl": "-0.00009534",
|
||
# "marginAsset": "USDT",
|
||
# "quoteQty": "38.47955",
|
||
# "commission": "-0.00076959",
|
||
# "commissionAsset": "USDT",
|
||
# "time": 1612733566708,
|
||
# "positionSide": "BOTH",
|
||
# "maker": True,
|
||
# "buyer": False
|
||
# }
|
||
#
|
||
# {respType: FULL}
|
||
#
|
||
# {
|
||
# "price": "4000.00000000",
|
||
# "qty": "1.00000000",
|
||
# "commission": "4.00000000",
|
||
# "commissionAsset": "USDT",
|
||
# "tradeId": "1234",
|
||
# }
|
||
#
|
||
# options: fetchMyTrades
|
||
#
|
||
# {
|
||
# "id": 1125899906844226012,
|
||
# "tradeId": 73,
|
||
# "orderId": 4638761100843040768,
|
||
# "symbol": "ETH-230211-1500-C",
|
||
# "price": "18.70000000",
|
||
# "quantity": "-0.57000000",
|
||
# "fee": "0.17305890",
|
||
# "realizedProfit": "-3.53400000",
|
||
# "side": "SELL",
|
||
# "type": "LIMIT",
|
||
# "volatility": "0.30000000",
|
||
# "liquidity": "MAKER",
|
||
# "time": 1676085216845,
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "quoteAsset": "USDT"
|
||
# }
|
||
#
|
||
# options: fetchTrades
|
||
#
|
||
# {
|
||
# "id": 1,
|
||
# "symbol": "ETH-230216-1500-C",
|
||
# "price": "35.5",
|
||
# "qty": "0.03",
|
||
# "quoteQty": "1.065",
|
||
# "side": 1,
|
||
# "time": 1676366446072
|
||
# }
|
||
#
|
||
# fetchMyTrades: linear portfolio margin
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "id": 4575108247,
|
||
# "orderId": 261942655610,
|
||
# "side": "SELL",
|
||
# "price": "47263.40",
|
||
# "qty": "0.010",
|
||
# "realizedPnl": "27.38400000",
|
||
# "marginAsset": "USDT",
|
||
# "quoteQty": "472.63",
|
||
# "commission": "0.18905360",
|
||
# "commissionAsset": "USDT",
|
||
# "time": 1707530039409,
|
||
# "buyer": False,
|
||
# "maker": False,
|
||
# "positionSide": "LONG"
|
||
# }
|
||
#
|
||
# fetchMyTrades: inverse portfolio margin
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "id": 701907838,
|
||
# "orderId": 71548909034,
|
||
# "pair": "ETHUSD",
|
||
# "side": "SELL",
|
||
# "price": "2498.15",
|
||
# "qty": "1",
|
||
# "realizedPnl": "0.00012517",
|
||
# "marginAsset": "ETH",
|
||
# "baseQty": "0.00400296",
|
||
# "commission": "0.00000160",
|
||
# "commissionAsset": "ETH",
|
||
# "time": 1707530317519,
|
||
# "positionSide": "LONG",
|
||
# "buyer": False,
|
||
# "maker": False
|
||
# }
|
||
#
|
||
# fetchMyTrades: spot margin portfolio margin
|
||
#
|
||
# {
|
||
# "symbol": "ADAUSDT",
|
||
# "id": 470227543,
|
||
# "orderId": 4421170947,
|
||
# "price": "0.53880000",
|
||
# "qty": "10.00000000",
|
||
# "quoteQty": "5.38800000",
|
||
# "commission": "0.00538800",
|
||
# "commissionAsset": "USDT",
|
||
# "time": 1707545780522,
|
||
# "isBuyer": False,
|
||
# "isMaker": False,
|
||
# "isBestMatch": True
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer_2(trade, 'T', 'time')
|
||
amount = self.safe_string_2(trade, 'q', 'qty')
|
||
amount = self.safe_string(trade, 'quantity', amount)
|
||
marketId = self.safe_string(trade, 'symbol')
|
||
isSpotTrade = ('isIsolated' in trade) or ('M' in trade) or ('orderListId' in trade) or ('isMaker' in trade)
|
||
marketType = 'spot' if isSpotTrade else 'contract'
|
||
market = self.safe_market(marketId, market, None, marketType)
|
||
symbol = market['symbol']
|
||
side = None
|
||
buyerMaker = self.safe_bool_2(trade, 'm', 'isBuyerMaker')
|
||
takerOrMaker = None
|
||
if buyerMaker is not None:
|
||
side = 'sell' if buyerMaker else 'buy' # self is reversed intentionally
|
||
elif 'side' in trade:
|
||
side = self.safe_string_lower(trade, 'side')
|
||
else:
|
||
if 'isBuyer' in trade:
|
||
side = 'buy' if trade['isBuyer'] else 'sell' # self is a True side
|
||
fee = None
|
||
if 'commission' in trade:
|
||
fee = {
|
||
'cost': self.safe_string(trade, 'commission'),
|
||
'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAsset')),
|
||
}
|
||
if 'isMaker' in trade:
|
||
takerOrMaker = 'maker' if trade['isMaker'] else 'taker'
|
||
if 'maker' in trade:
|
||
takerOrMaker = 'maker' if trade['maker'] else 'taker'
|
||
if ('optionSide' in trade) or market['option']:
|
||
settle = self.safe_currency_code(self.safe_string(trade, 'quoteAsset', 'USDT'))
|
||
takerOrMaker = self.safe_string_lower(trade, 'liquidity')
|
||
if 'fee' in trade:
|
||
fee = {
|
||
'cost': self.safe_string(trade, 'fee'),
|
||
'currency': settle,
|
||
}
|
||
if (side != 'buy') and (side != 'sell'):
|
||
side = 'buy' if (side == '1') else 'sell'
|
||
if 'optionSide' in trade:
|
||
if side != 'buy':
|
||
amount = Precise.string_mul('-1', amount)
|
||
return self.safe_trade({
|
||
'info': trade,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': symbol,
|
||
'id': self.safe_string_n(trade, ['t', 'a', 'tradeId', 'id']),
|
||
'order': self.safe_string(trade, 'orderId'),
|
||
'type': self.safe_string_lower(trade, 'type'),
|
||
'side': side,
|
||
'takerOrMaker': takerOrMaker,
|
||
'price': self.safe_string_2(trade, 'p', 'price'),
|
||
'amount': amount,
|
||
'cost': self.safe_string_2(trade, 'quoteQty', 'baseQty'),
|
||
'fee': fee,
|
||
}, market)
|
||
|
||
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
||
"""
|
||
get the list of most recent trades for a particular symbol
|
||
Default fetchTradesMethod
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#compressedaggregate-trades-list # publicGetAggTrades(spot)
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Compressed-Aggregate-Trades-List # fapiPublicGetAggTrades(swap)
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Compressed-Aggregate-Trades-List # dapiPublicGetAggTrades(future)
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Recent-Trades-List # eapiPublicGetTrades(option)
|
||
|
||
Other fetchTradesMethod
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#recent-trades-list # publicGetTrades(spot)
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Recent-Trades-List # fapiPublicGetTrades(swap)
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Recent-Trades-List # dapiPublicGetTrades(future)
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/market-data-endpoints#old-trade-lookup # publicGetHistoricalTrades(spot)
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Old-Trades-Lookup # fapiPublicGetHistoricalTrades(swap)
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Old-Trades-Lookup # dapiPublicGetHistoricalTrades(future)
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Old-Trades-Lookup # eapiPublicGetHistoricalTrades(option)
|
||
|
||
:param str symbol: unified symbol of the market to fetch trades for
|
||
:param int [since]: only used when fetchTradesMethod is 'publicGetAggTrades', 'fapiPublicGetAggTrades', or 'dapiPublicGetAggTrades'
|
||
:param int [limit]: default 500, max 1000
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: only used when fetchTradesMethod is 'publicGetAggTrades', 'fapiPublicGetAggTrades', or 'dapiPublicGetAggTrades'
|
||
:param int [params.fetchTradesMethod]: 'publicGetAggTrades'(spot default), 'fapiPublicGetAggTrades'(swap default), 'dapiPublicGetAggTrades'(future default), 'eapiPublicGetTrades'(option default), 'publicGetTrades', 'fapiPublicGetTrades', 'dapiPublicGetTrades', 'publicGetHistoricalTrades', 'fapiPublicGetHistoricalTrades', 'dapiPublicGetHistoricalTrades', 'eapiPublicGetHistoricalTrades'
|
||
: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)
|
||
|
||
EXCHANGE SPECIFIC PARAMETERS
|
||
:param int [params.fromId]: trade id to fetch from, default gets most recent trades, not used when fetchTradesMethod is 'publicGetTrades', 'fapiPublicGetTrades', 'dapiPublicGetTrades', or 'eapiPublicGetTrades'
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchTrades', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_dynamic('fetchTrades', symbol, since, limit, params)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
# 'fromId': 123, # ID to get aggregate trades from INCLUSIVE.
|
||
# 'startTime': 456, # Timestamp in ms to get aggregate trades from INCLUSIVE.
|
||
# 'endTime': 789, # Timestamp in ms to get aggregate trades until INCLUSIVE.
|
||
# 'limit': 500, # default = 500, maximum = 1000
|
||
}
|
||
if not market['option']:
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
# https://github.com/ccxt/ccxt/issues/6400
|
||
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
|
||
request['endTime'] = self.sum(since, 3600000)
|
||
until = self.safe_integer(params, 'until')
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
method = self.safe_string(self.options, 'fetchTradesMethod')
|
||
method = self.safe_string_2(params, 'fetchTradesMethod', 'method', method)
|
||
if limit is not None:
|
||
isFutureOrSwap = (market['swap'] or market['future'])
|
||
isHistoricalEndpoint = (method is not None) and (method.find('GetHistoricalTrades') >= 0)
|
||
maxLimitForContractHistorical = 500 if isHistoricalEndpoint else 1000
|
||
request['limit'] = min(limit, maxLimitForContractHistorical) if isFutureOrSwap else limit # default = 500, maximum = 1000
|
||
params = self.omit(params, ['until', 'fetchTradesMethod'])
|
||
response = None
|
||
if market['option'] or method == 'eapiPublicGetTrades':
|
||
response = self.eapiPublicGetTrades(self.extend(request, params))
|
||
elif market['linear'] or method == 'fapiPublicGetAggTrades':
|
||
response = self.fapiPublicGetAggTrades(self.extend(request, params))
|
||
elif market['inverse'] or method == 'dapiPublicGetAggTrades':
|
||
response = self.dapiPublicGetAggTrades(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetAggTrades(self.extend(request, params))
|
||
#
|
||
# Caveats:
|
||
# - default limit(500) applies only if no other parameters set, trades up
|
||
# to the maximum limit may be returned to satisfy other parameters
|
||
# - if both limit and time window is set and time window contains more
|
||
# trades than the limit then the last trades from the window are returned
|
||
# - "tradeId" accepted and returned by self method is "aggregate" trade id
|
||
# which is different from actual trade id
|
||
# - setting both fromId and time window results in error
|
||
#
|
||
# aggregate trades
|
||
#
|
||
# [
|
||
# {
|
||
# "a": 26129, # Aggregate tradeId
|
||
# "p": "0.01633102", # Price
|
||
# "q": "4.70443515", # Quantity
|
||
# "f": 27781, # First tradeId
|
||
# "l": 27781, # Last tradeId
|
||
# "T": 1498793709153, # Timestamp
|
||
# "m": True, # Was the buyer the maker?
|
||
# "M": True # Was the trade the best price match?
|
||
# }
|
||
# ]
|
||
#
|
||
# inverse(swap & future)
|
||
#
|
||
# [
|
||
# {
|
||
# "a": "269772814",
|
||
# "p": "25864.1",
|
||
# "q": "3",
|
||
# "f": "662149354",
|
||
# "l": "662149355",
|
||
# "T": "1694209776022",
|
||
# "m": False,
|
||
# },
|
||
# ]
|
||
#
|
||
# recent public trades and historical public trades
|
||
#
|
||
# [
|
||
# {
|
||
# "id": 28457,
|
||
# "price": "4.00000100",
|
||
# "qty": "12.00000000",
|
||
# "time": 1499865549590,
|
||
# "isBuyerMaker": True,
|
||
# "isBestMatch": True
|
||
# }
|
||
# ]
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# [
|
||
# {
|
||
# "id": 1,
|
||
# "symbol": "ETH-230216-1500-C",
|
||
# "price": "35.5",
|
||
# "qty": "0.03",
|
||
# "quoteQty": "1.065",
|
||
# "side": 1,
|
||
# "time": 1676366446072
|
||
# },
|
||
# ]
|
||
#
|
||
return self.parse_trades(response, market, since, limit)
|
||
|
||
def edit_spot_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
@ignore
|
||
edit a trade order
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
|
||
|
||
:param str id: cancel order id
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit' or 'STOP_LOSS' or 'STOP_LOSS_LIMIT' or 'TAKE_PROFIT' or 'TAKE_PROFIT_LIMIT' or 'STOP'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of currency you want to trade in units of base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' editSpotOrder() does not support ' + market['type'] + ' orders')
|
||
payload = self.edit_spot_order_request(id, symbol, type, side, amount, price, params)
|
||
response = self.privatePostOrderCancelReplace(payload)
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "cancelResult": "SUCCESS",
|
||
# "newOrderResult": "SUCCESS",
|
||
# "cancelResponse": {
|
||
# "symbol": "BTCUSDT",
|
||
# "origClientOrderId": "web_3f6286480b194b079870ac75fb6978b7",
|
||
# "orderId": 16383156620,
|
||
# "orderListId": -1,
|
||
# "clientOrderId": "Azt6foVTTgHPNhqBf41TTt",
|
||
# "price": "14000.00000000",
|
||
# "origQty": "0.00110000",
|
||
# "executedQty": "0.00000000",
|
||
# "cummulativeQuoteQty": "0.00000000",
|
||
# "status": "CANCELED",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY"
|
||
# },
|
||
# "newOrderResponse": {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": 16383176297,
|
||
# "orderListId": -1,
|
||
# "clientOrderId": "x-TKT5PX2F22ecb58eb9074fb1be018c",
|
||
# "transactTime": 1670891847932,
|
||
# "price": "13500.00000000",
|
||
# "origQty": "0.00085000",
|
||
# "executedQty": "0.00000000",
|
||
# "cummulativeQuoteQty": "0.00000000",
|
||
# "status": "NEW",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "fills": []
|
||
# }
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'newOrderResponse')
|
||
return self.parse_order(data, market)
|
||
|
||
def edit_spot_order_request(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
@ignore
|
||
helper function to build request for editSpotOrder
|
||
:param str id: order id to be edited
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit' or 'STOP_LOSS' or 'STOP_LOSS_LIMIT' or 'TAKE_PROFIT' or 'TAKE_PROFIT_LIMIT' or 'STOP'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of currency you want to trade in units of base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict params: extra parameters specific to the exchange API endpoint
|
||
:param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
|
||
:returns dict: request to be sent to the exchange
|
||
"""
|
||
market = self.market(symbol)
|
||
clientOrderId = self.safe_string_n(params, ['newClientOrderId', 'clientOrderId', 'origClientOrderId'])
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'side': side.upper(),
|
||
}
|
||
initialUppercaseType = type.upper()
|
||
uppercaseType = initialUppercaseType
|
||
postOnly = self.is_post_only(initialUppercaseType == 'MARKET', initialUppercaseType == 'LIMIT_MAKER', params)
|
||
if postOnly:
|
||
uppercaseType = 'LIMIT_MAKER'
|
||
triggerPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
|
||
if triggerPrice is not None:
|
||
if uppercaseType == 'MARKET':
|
||
uppercaseType = 'STOP_LOSS'
|
||
elif uppercaseType == 'LIMIT':
|
||
uppercaseType = 'STOP_LOSS_LIMIT'
|
||
request['type'] = uppercaseType
|
||
validOrderTypes = self.safe_list(market['info'], 'orderTypes')
|
||
if not self.in_array(uppercaseType, validOrderTypes):
|
||
if initialUppercaseType != uppercaseType:
|
||
raise InvalidOrder(self.id + ' triggerPrice parameter is not allowed for ' + symbol + ' ' + type + ' orders')
|
||
else:
|
||
raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type for the ' + symbol + ' market')
|
||
if clientOrderId is None:
|
||
broker = self.safe_dict(self.options, 'broker')
|
||
if broker is not None:
|
||
brokerId = self.safe_string(broker, 'spot')
|
||
if brokerId is not None:
|
||
request['newClientOrderId'] = brokerId + self.uuid22()
|
||
else:
|
||
request['newClientOrderId'] = clientOrderId
|
||
request['newOrderRespType'] = self.safe_value(self.options['newOrderRespType'], type, 'RESULT') # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
|
||
timeInForceIsRequired = False
|
||
priceIsRequired = False
|
||
triggerPriceIsRequired = False
|
||
quantityIsRequired = False
|
||
if uppercaseType == 'MARKET':
|
||
quoteOrderQty = self.safe_bool(self.options, 'quoteOrderQty', True)
|
||
if quoteOrderQty:
|
||
quoteOrderQtyNew = self.safe_value_2(params, 'quoteOrderQty', 'cost')
|
||
precision = market['precision']['price']
|
||
if quoteOrderQtyNew is not None:
|
||
request['quoteOrderQty'] = self.decimal_to_precision(quoteOrderQtyNew, TRUNCATE, precision, self.precisionMode)
|
||
elif price is not None:
|
||
amountString = self.number_to_string(amount)
|
||
priceString = self.number_to_string(price)
|
||
quoteOrderQuantity = Precise.string_mul(amountString, priceString)
|
||
request['quoteOrderQty'] = self.decimal_to_precision(quoteOrderQuantity, TRUNCATE, precision, self.precisionMode)
|
||
else:
|
||
quantityIsRequired = True
|
||
else:
|
||
quantityIsRequired = True
|
||
elif uppercaseType == 'LIMIT':
|
||
priceIsRequired = True
|
||
timeInForceIsRequired = True
|
||
quantityIsRequired = True
|
||
elif (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT'):
|
||
triggerPriceIsRequired = True
|
||
quantityIsRequired = True
|
||
elif (uppercaseType == 'STOP_LOSS_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
||
quantityIsRequired = True
|
||
triggerPriceIsRequired = True
|
||
priceIsRequired = True
|
||
timeInForceIsRequired = True
|
||
elif uppercaseType == 'LIMIT_MAKER':
|
||
priceIsRequired = True
|
||
quantityIsRequired = True
|
||
if quantityIsRequired:
|
||
request['quantity'] = self.amount_to_precision(symbol, amount)
|
||
if priceIsRequired:
|
||
if price is None:
|
||
raise InvalidOrder(self.id + ' editOrder() requires a price argument for a ' + type + ' order')
|
||
request['price'] = self.price_to_precision(symbol, price)
|
||
if timeInForceIsRequired and (self.safe_string(params, 'timeInForce') is None):
|
||
request['timeInForce'] = self.options['defaultTimeInForce'] # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
|
||
if triggerPriceIsRequired:
|
||
if triggerPrice is None:
|
||
raise InvalidOrder(self.id + ' editOrder() requires a triggerPrice extra param for a ' + type + ' order')
|
||
else:
|
||
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
|
||
request['cancelReplaceMode'] = 'STOP_ON_FAILURE' # If the cancel request fails, the new order placement will not be attempted.
|
||
cancelId = self.safe_string_2(params, 'cancelNewClientOrderId', 'cancelOrigClientOrderId')
|
||
if cancelId is None:
|
||
request['cancelOrderId'] = id # user can provide either cancelOrderId, cancelOrigClientOrderId or cancelOrigClientOrderId
|
||
# remove timeInForce from params because PO is only used by self.is_post_only and it's not a valid value for Binance
|
||
if self.safe_string(params, 'timeInForce') == 'PO':
|
||
params = self.omit(params, ['timeInForce'])
|
||
params = self.omit(params, ['quoteOrderQty', 'cost', 'stopPrice', 'newClientOrderId', 'clientOrderId', 'postOnly'])
|
||
return self.extend(request, params)
|
||
|
||
def edit_contract_order_request(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
market = self.market(symbol)
|
||
if not market['contract']:
|
||
raise NotSupported(self.id + ' editContractOrder() does not support ' + market['type'] + ' orders')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'side': side.upper(),
|
||
'orderId': id,
|
||
'quantity': self.amount_to_precision(symbol, amount),
|
||
}
|
||
clientOrderId = self.safe_string_n(params, ['newClientOrderId', 'clientOrderId', 'origClientOrderId'])
|
||
if price is not None:
|
||
request['price'] = self.price_to_precision(symbol, price)
|
||
if clientOrderId is not None:
|
||
request['origClientOrderId'] = clientOrderId
|
||
params = self.omit(params, ['clientOrderId', 'newClientOrderId'])
|
||
return request
|
||
|
||
def edit_contract_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
edit a trade order
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Modify-Order
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Modify-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-UM-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-CM-Order
|
||
|
||
:param str id: cancel 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 currency you want to trade in units of base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to edit an order in a portfolio margin account
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'editContractOrder', 'papi', 'portfolioMargin', False)
|
||
if market['linear'] or isPortfolioMargin:
|
||
if (price is None) and not ('priceMatch' in params):
|
||
raise ArgumentsRequired(self.id + ' editOrder() requires a price argument for portfolio margin and linear orders')
|
||
request = self.edit_contract_order_request(id, symbol, type, side, amount, price, params)
|
||
response = None
|
||
if market['linear']:
|
||
if isPortfolioMargin:
|
||
response = self.papiPutUmOrder(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivatePutOrder(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
response = self.papiPutCmOrder(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivatePutOrder(self.extend(request, params))
|
||
#
|
||
# swap and future
|
||
#
|
||
# {
|
||
# "orderId": 151007482392,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "web_pCCGp9AIHjziKLlpGpXI",
|
||
# "price": "25000",
|
||
# "avgPrice": "0.00000",
|
||
# "origQty": "0.001",
|
||
# "executedQty": "0",
|
||
# "cumQty": "0",
|
||
# "cumQuote": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "updateTime": 1684300587845
|
||
# }
|
||
#
|
||
return self.parse_order(response, market)
|
||
|
||
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
||
"""
|
||
edit a trade order
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-an-existing-order-and-send-a-new-order-trade
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Modify-Order
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Modify-Order
|
||
|
||
:param str id: cancel 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 currency you want to trade in units of base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if market['option']:
|
||
raise NotSupported(self.id + ' editOrder() does not support ' + market['type'] + ' orders')
|
||
if market['spot']:
|
||
return self.edit_spot_order(id, symbol, type, side, amount, price, params)
|
||
else:
|
||
return self.edit_contract_order(id, symbol, type, side, amount, price, params)
|
||
|
||
def edit_orders(self, orders: List[OrderRequest], params={}):
|
||
"""
|
||
edit a list of trade orders
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Modify-Multiple-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Modify-Multiple-Orders
|
||
|
||
: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
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
ordersRequests = []
|
||
orderSymbols = []
|
||
for i in range(0, len(orders)):
|
||
rawOrder = orders[i]
|
||
marketId = self.safe_string(rawOrder, 'symbol')
|
||
orderSymbols.append(marketId)
|
||
id = self.safe_string(rawOrder, 'id')
|
||
type = self.safe_string(rawOrder, 'type')
|
||
side = self.safe_string(rawOrder, 'side')
|
||
amount = self.safe_value(rawOrder, 'amount')
|
||
price = self.safe_value(rawOrder, 'price')
|
||
orderParams = self.safe_dict(rawOrder, 'params', {})
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, orderParams = self.handle_option_and_params_2(orderParams, 'editOrders', 'papi', 'portfolioMargin', False)
|
||
if isPortfolioMargin:
|
||
raise NotSupported(self.id + ' editOrders() does not support portfolio margin orders')
|
||
orderRequest = self.edit_contract_order_request(id, marketId, type, side, amount, price, orderParams)
|
||
ordersRequests.append(orderRequest)
|
||
orderSymbols = self.market_symbols(orderSymbols, None, False, True, True)
|
||
market = self.market(orderSymbols[0])
|
||
if market['spot'] or market['option']:
|
||
raise NotSupported(self.id + ' editOrders() does not support ' + market['type'] + ' orders')
|
||
response = None
|
||
request: dict = {
|
||
'batchOrders': ordersRequests,
|
||
}
|
||
request = self.extend(request, params)
|
||
if market['linear']:
|
||
response = self.fapiPrivatePutBatchOrders(request)
|
||
elif market['inverse']:
|
||
response = self.dapiPrivatePutBatchOrders(request)
|
||
#
|
||
# [
|
||
# {
|
||
# "code": -4005,
|
||
# "msg": "Quantity greater than max quantity."
|
||
# },
|
||
# {
|
||
# "orderId": 650640530,
|
||
# "symbol": "LTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcu32184eb13585491289bbaf",
|
||
# "price": "54.00",
|
||
# "avgPrice": "0.00",
|
||
# "origQty": "0.100",
|
||
# "executedQty": "0.000",
|
||
# "cumQty": "0.000",
|
||
# "cumQuote": "0.00000",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0.00",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "priceMatch": "NONE",
|
||
# "selfTradePreventionMode": "NONE",
|
||
# "goodTillDate": 0,
|
||
# "updateTime": 1698073926929
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_orders(response)
|
||
|
||
def parse_order_status(self, status: Str):
|
||
statuses: dict = {
|
||
'NEW': 'open',
|
||
'PARTIALLY_FILLED': 'open',
|
||
'ACCEPTED': 'open',
|
||
'FILLED': 'closed',
|
||
'CANCELED': 'canceled',
|
||
'CANCELLED': 'canceled',
|
||
'PENDING_CANCEL': 'canceling', # currently unused
|
||
'REJECTED': 'rejected',
|
||
'EXPIRED': 'expired',
|
||
'EXPIRED_IN_MATCH': 'expired',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_order(self, order: dict, market: Market = None) -> Order:
|
||
#
|
||
# spot
|
||
#
|
||
# {
|
||
# "symbol": "LTCBTC",
|
||
# "orderId": 1,
|
||
# "clientOrderId": "myOrder1",
|
||
# "price": "0.1",
|
||
# "origQty": "1.0",
|
||
# "executedQty": "0.0",
|
||
# "cummulativeQuoteQty": "0.0",
|
||
# "status": "NEW",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "stopPrice": "0.0",
|
||
# "icebergQty": "0.0",
|
||
# "time": 1499827319559,
|
||
# "updateTime": 1499827319559,
|
||
# "isWorking": True
|
||
# }
|
||
#
|
||
# spot: editOrder
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": 16383176297,
|
||
# "orderListId": -1,
|
||
# "clientOrderId": "x-TKT5PX2F22ecb58eb9074fb1be018c",
|
||
# "transactTime": 1670891847932,
|
||
# "price": "13500.00000000",
|
||
# "origQty": "0.00085000",
|
||
# "executedQty": "0.00000000",
|
||
# "cummulativeQuoteQty": "0.00000000",
|
||
# "status": "NEW",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "fills": []
|
||
# }
|
||
#
|
||
# swap and future: editOrder
|
||
#
|
||
# {
|
||
# "orderId": 151007482392,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "web_pCCGp9AIHjziKLlpGpXI",
|
||
# "price": "25000",
|
||
# "avgPrice": "0.00000",
|
||
# "origQty": "0.001",
|
||
# "executedQty": "0",
|
||
# "cumQty": "0",
|
||
# "cumQuote": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "updateTime": 1684300587845
|
||
# }
|
||
#
|
||
# futures
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": 1,
|
||
# "clientOrderId": "myOrder1",
|
||
# "price": "0.1",
|
||
# "origQty": "1.0",
|
||
# "executedQty": "1.0",
|
||
# "cumQuote": "10.0",
|
||
# "status": "NEW",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "stopPrice": "0.0",
|
||
# "updateTime": 1499827319559
|
||
# }
|
||
#
|
||
# createOrder with {"newOrderRespType": "FULL"}
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": 5403233939,
|
||
# "orderListId": -1,
|
||
# "clientOrderId": "x-TKT5PX2F5e669e75b6c14f69a2c43e",
|
||
# "transactTime": 1617151923742,
|
||
# "price": "0.00000000",
|
||
# "origQty": "0.00050000",
|
||
# "executedQty": "0.00050000",
|
||
# "cummulativeQuoteQty": "29.47081500",
|
||
# "status": "FILLED",
|
||
# "timeInForce": "GTC",
|
||
# "type": "MARKET",
|
||
# "side": "BUY",
|
||
# "fills": [
|
||
# {
|
||
# "price": "58941.63000000",
|
||
# "qty": "0.00050000",
|
||
# "commission": "0.00007050",
|
||
# "commissionAsset": "BNB",
|
||
# "tradeId": 737466631
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
# delivery
|
||
#
|
||
# {
|
||
# "orderId": "18742727411",
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "pair": "ETHUSD",
|
||
# "status": "FILLED",
|
||
# "clientOrderId": "x-xcKtGhcu3e2d1503fdd543b3b02419",
|
||
# "price": "0",
|
||
# "avgPrice": "4522.14",
|
||
# "origQty": "1",
|
||
# "executedQty": "1",
|
||
# "cumBase": "0.00221134",
|
||
# "timeInForce": "GTC",
|
||
# "type": "MARKET",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "SELL",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "MARKET",
|
||
# "time": "1636061952660",
|
||
# "updateTime": "1636061952660"
|
||
# }
|
||
#
|
||
# option: createOrder, fetchOrder, fetchOpenOrders, fetchOrders
|
||
#
|
||
# {
|
||
# "orderId": 4728833085436977152,
|
||
# "symbol": "ETH-230211-1500-C",
|
||
# "price": "10.0",
|
||
# "quantity": "1.00",
|
||
# "executedQty": "0.00",
|
||
# "fee": "0",
|
||
# "side": "BUY",
|
||
# "type": "LIMIT",
|
||
# "timeInForce": "GTC",
|
||
# "reduceOnly": False,
|
||
# "postOnly": False,
|
||
# "createTime": 1676083034462,
|
||
# "updateTime": 1676083034462,
|
||
# "status": "ACCEPTED",
|
||
# "avgPrice": "0",
|
||
# "source": "API",
|
||
# "clientOrderId": "",
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "quoteAsset": "USDT",
|
||
# "lastTrade": {"id":"69","time":"1676084430567","price":"24.9","qty":"1.00"},
|
||
# "mmp": False
|
||
# }
|
||
#
|
||
# cancelOrders/createOrders
|
||
#
|
||
# {
|
||
# "code": -4005,
|
||
# "msg": "Quantity greater than max quantity."
|
||
# }
|
||
#
|
||
# createOrder, fetchOpenOrders, fetchOrder, cancelOrder, fetchOrders: portfolio margin linear swap and future
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "side": "BUY",
|
||
# "executedQty": "0.000",
|
||
# "orderId": 258649539704,
|
||
# "goodTillDate": 0,
|
||
# "avgPrice": "0",
|
||
# "origQty": "0.010",
|
||
# "clientOrderId": "x-xcKtGhcu02573c6f15e544e990057b",
|
||
# "positionSide": "BOTH",
|
||
# "cumQty": "0.000",
|
||
# "updateTime": 1707110415436,
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "price": "35000.00",
|
||
# "cumQuote": "0.00000",
|
||
# "selfTradePreventionMode": "NONE",
|
||
# "timeInForce": "GTC",
|
||
# "status": "NEW"
|
||
# }
|
||
#
|
||
# createOrder, fetchOpenOrders, fetchOrder, cancelOrder, fetchOrders: portfolio margin inverse swap and future
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "side": "BUY",
|
||
# "cumBase": "0",
|
||
# "executedQty": "0",
|
||
# "orderId": 71275227732,
|
||
# "avgPrice": "0.00",
|
||
# "origQty": "1",
|
||
# "clientOrderId": "x-xcKtGhcuca5af3acfb5044198c5398",
|
||
# "positionSide": "BOTH",
|
||
# "cumQty": "0",
|
||
# "updateTime": 1707110994334,
|
||
# "type": "LIMIT",
|
||
# "pair": "ETHUSD",
|
||
# "reduceOnly": False,
|
||
# "price": "2000",
|
||
# "timeInForce": "GTC",
|
||
# "status": "NEW"
|
||
# }
|
||
#
|
||
# createOrder, fetchOpenOrders, fetchOpenOrder: portfolio margin linear swap and future conditional
|
||
#
|
||
# {
|
||
# "newClientStrategyId": "x-xcKtGhcu27f109953d6e4dc0974006",
|
||
# "strategyId": 3645916,
|
||
# "strategyStatus": "NEW",
|
||
# "strategyType": "STOP",
|
||
# "origQty": "0.010",
|
||
# "price": "35000.00",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "45000.00",
|
||
# "symbol": "BTCUSDT",
|
||
# "timeInForce": "GTC",
|
||
# "bookTime": 1707112625879,
|
||
# "updateTime": 1707112625879,
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "goodTillDate": 0,
|
||
# "selfTradePreventionMode": "NONE"
|
||
# }
|
||
#
|
||
# createOrder, fetchOpenOrders: portfolio margin inverse swap and future conditional
|
||
#
|
||
# {
|
||
# "newClientStrategyId": "x-xcKtGhcuc6b86f053bb34933850739",
|
||
# "strategyId": 1423462,
|
||
# "strategyStatus": "NEW",
|
||
# "strategyType": "STOP",
|
||
# "origQty": "1",
|
||
# "price": "2000",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "3000",
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "timeInForce": "GTC",
|
||
# "bookTime": 1707113098840,
|
||
# "updateTime": 1707113098840,
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False
|
||
# }
|
||
#
|
||
# createOrder, cancelAllOrders, cancelOrder: portfolio margin spot margin
|
||
#
|
||
# {
|
||
# "clientOrderId": "x-TKT5PX2Fe9ef29d8346440f0b28b86",
|
||
# "cummulativeQuoteQty": "0.00000000",
|
||
# "executedQty": "0.00000000",
|
||
# "fills": [],
|
||
# "orderId": 24684460474,
|
||
# "origQty": "0.00100000",
|
||
# "price": "35000.00000000",
|
||
# "selfTradePreventionMode": "EXPIRE_MAKER",
|
||
# "side": "BUY",
|
||
# "status": "NEW",
|
||
# "symbol": "BTCUSDT",
|
||
# "timeInForce": "GTC",
|
||
# "transactTime": 1707113538870,
|
||
# "type": "LIMIT"
|
||
# }
|
||
#
|
||
# fetchOpenOrders, fetchOrder, fetchOrders: portfolio margin spot margin
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": 24700763749,
|
||
# "clientOrderId": "x-TKT5PX2F6f724c2a4af6425f98c7b6",
|
||
# "price": "35000.00000000",
|
||
# "origQty": "0.00100000",
|
||
# "executedQty": "0.00000000",
|
||
# "cummulativeQuoteQty": "0.00000000",
|
||
# "status": "NEW",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "stopPrice": "0.00000000",
|
||
# "icebergQty": "0.00000000",
|
||
# "time": 1707199187679,
|
||
# "updateTime": 1707199187679,
|
||
# "isWorking": True,
|
||
# "accountId": 200180970,
|
||
# "selfTradePreventionMode": "EXPIRE_MAKER",
|
||
# "preventedMatchId": null,
|
||
# "preventedQuantity": null
|
||
# }
|
||
#
|
||
# cancelOrder: portfolio margin linear and inverse swap conditional
|
||
#
|
||
# {
|
||
# "strategyId": 3733211,
|
||
# "newClientStrategyId": "x-xcKtGhcuaf166172ed504cd1bc0396",
|
||
# "strategyType": "STOP",
|
||
# "strategyStatus": "CANCELED",
|
||
# "origQty": "0.010",
|
||
# "price": "35000.00",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "50000.00", # ignored with trailing orders
|
||
# "symbol": "BTCUSDT",
|
||
# "timeInForce": "GTC",
|
||
# "activatePrice": null, # only return with trailing orders
|
||
# "priceRate": null, # only return with trailing orders
|
||
# "bookTime": 1707270098774,
|
||
# "updateTime": 1707270119261,
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "goodTillDate": 0,
|
||
# "selfTradePreventionMode": "NONE"
|
||
# }
|
||
#
|
||
# fetchOrders: portfolio margin linear and inverse swap conditional
|
||
#
|
||
# {
|
||
# "newClientStrategyId": "x-xcKtGhcuaf166172ed504cd1bc0396",
|
||
# "strategyId": 3733211,
|
||
# "strategyStatus": "CANCELLED",
|
||
# "strategyType": "STOP",
|
||
# "origQty": "0.010",
|
||
# "price": "35000",
|
||
# "orderId": 0,
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "50000",
|
||
# "symbol": "BTCUSDT",
|
||
# "type": "LIMIT",
|
||
# "bookTime": 1707270098774,
|
||
# "updateTime": 1707270119261,
|
||
# "timeInForce": "GTC",
|
||
# "triggerTime": 0,
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "goodTillDate": 0,
|
||
# "selfTradePreventionMode": "NONE"
|
||
# }
|
||
#
|
||
# fetchOpenOrder: linear swap
|
||
#
|
||
# {
|
||
# "orderId": 3697213934,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcufb20c5a7761a4aa09aa156",
|
||
# "price": "33000.00",
|
||
# "avgPrice": "0.00000",
|
||
# "origQty": "0.010",
|
||
# "executedQty": "0.000",
|
||
# "cumQuote": "0.00000",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0.00",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "priceMatch": "NONE",
|
||
# "selfTradePreventionMode": "NONE",
|
||
# "goodTillDate": 0,
|
||
# "time": 1707892893502,
|
||
# "updateTime": 1707892893515
|
||
# }
|
||
#
|
||
# fetchOpenOrder: inverse swap
|
||
#
|
||
# {
|
||
# "orderId": 597368542,
|
||
# "symbol": "BTCUSD_PERP",
|
||
# "pair": "BTCUSD",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcubbde7ba93b1a4ab881eff3",
|
||
# "price": "35000",
|
||
# "avgPrice": "0",
|
||
# "origQty": "1",
|
||
# "executedQty": "0",
|
||
# "cumBase": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "time": 1707893453199,
|
||
# "updateTime": 1707893453199
|
||
# }
|
||
#
|
||
# fetchOpenOrder: linear portfolio margin
|
||
#
|
||
# {
|
||
# "orderId": 264895013409,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcu6278f1adbdf14f74ab432e",
|
||
# "price": "35000",
|
||
# "avgPrice": "0",
|
||
# "origQty": "0.010",
|
||
# "executedQty": "0",
|
||
# "cumQuote": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "LONG",
|
||
# "origType": "LIMIT",
|
||
# "time": 1707893839364,
|
||
# "updateTime": 1707893839364,
|
||
# "goodTillDate": 0,
|
||
# "selfTradePreventionMode": "NONE"
|
||
# }
|
||
#
|
||
# fetchOpenOrder: inverse portfolio margin
|
||
#
|
||
# {
|
||
# "orderId": 71790316950,
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "pair": "ETHUSD",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcuec11030474204ab08ba2c2",
|
||
# "price": "2500",
|
||
# "avgPrice": "0",
|
||
# "origQty": "1",
|
||
# "executedQty": "0",
|
||
# "cumBase": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "LONG",
|
||
# "origType": "LIMIT",
|
||
# "time": 1707894181694,
|
||
# "updateTime": 1707894181694
|
||
# }
|
||
#
|
||
# fetchOpenOrder: inverse portfolio margin conditional
|
||
#
|
||
# {
|
||
# "newClientStrategyId": "x-xcKtGhcu2da9c765294b433994ffce",
|
||
# "strategyId": 1423501,
|
||
# "strategyStatus": "NEW",
|
||
# "strategyType": "STOP",
|
||
# "origQty": "1",
|
||
# "price": "2500",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "LONG",
|
||
# "stopPrice": "4000",
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "bookTime": 1707894782679,
|
||
# "updateTime": 1707894782679,
|
||
# "timeInForce": "GTC",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False
|
||
# }
|
||
#
|
||
code = self.safe_string(order, 'code')
|
||
if code is not None:
|
||
# cancelOrders/createOrders might have a partial success
|
||
return self.safe_order({'info': order, 'status': 'rejected'}, market)
|
||
status = self.parse_order_status(self.safe_string_2(order, 'status', 'strategyStatus'))
|
||
marketId = self.safe_string(order, 'symbol')
|
||
isContract = ('positionSide' in order) or ('cumQuote' in order)
|
||
marketType = 'contract' if isContract else 'spot'
|
||
symbol = self.safe_symbol(marketId, market, None, marketType)
|
||
filled = self.safe_string(order, 'executedQty', '0')
|
||
timestamp = self.safe_integer_n(order, ['time', 'createTime', 'workingTime', 'transactTime', 'updateTime']) # order of the keys matters here
|
||
lastTradeTimestamp = None
|
||
if ('transactTime' in order) or ('updateTime' in order):
|
||
timestampValue = self.safe_integer_2(order, 'updateTime', 'transactTime')
|
||
if status == 'open':
|
||
if Precise.string_gt(filled, '0'):
|
||
lastTradeTimestamp = timestampValue
|
||
elif status == 'closed':
|
||
lastTradeTimestamp = timestampValue
|
||
lastUpdateTimestamp = self.safe_integer_2(order, 'transactTime', 'updateTime')
|
||
average = self.safe_string(order, 'avgPrice')
|
||
price = self.safe_string(order, 'price')
|
||
amount = self.safe_string_2(order, 'origQty', 'quantity')
|
||
# - Spot/Margin market: cummulativeQuoteQty
|
||
# - Futures market: cumQuote.
|
||
# Note self is not the actual cost, since Binance futures uses leverage to calculate margins.
|
||
cost = self.safe_string_2(order, 'cummulativeQuoteQty', 'cumQuote')
|
||
cost = self.safe_string(order, 'cumBase', cost)
|
||
type = self.safe_string_lower(order, 'type')
|
||
side = self.safe_string_lower(order, 'side')
|
||
fills = self.safe_list(order, 'fills', [])
|
||
timeInForce = self.safe_string(order, 'timeInForce')
|
||
if timeInForce == 'GTX':
|
||
# GTX means "Good Till Crossing" and is an equivalent way of saying Post Only
|
||
timeInForce = 'PO'
|
||
postOnly = (type == 'limit_maker') or (timeInForce == 'PO')
|
||
if type == 'limit_maker':
|
||
type = 'limit'
|
||
stopPriceString = self.safe_string(order, 'stopPrice')
|
||
triggerPrice = self.parse_number(self.omit_zero(stopPriceString))
|
||
feeCost = self.safe_number(order, 'fee')
|
||
fee = None
|
||
if feeCost is not None:
|
||
fee = {
|
||
'currency': self.safe_string(order, 'quoteAsset'),
|
||
'cost': feeCost,
|
||
'rate': None,
|
||
}
|
||
return self.safe_order({
|
||
'info': order,
|
||
'id': self.safe_string_2(order, 'strategyId', 'orderId'),
|
||
'clientOrderId': self.safe_string_2(order, 'clientOrderId', 'newClientStrategyId'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'lastTradeTimestamp': lastTradeTimestamp,
|
||
'lastUpdateTimestamp': lastUpdateTimestamp,
|
||
'symbol': symbol,
|
||
'type': type,
|
||
'timeInForce': timeInForce,
|
||
'postOnly': postOnly,
|
||
'reduceOnly': self.safe_bool(order, 'reduceOnly'),
|
||
'side': side,
|
||
'price': price,
|
||
'triggerPrice': triggerPrice,
|
||
'amount': amount,
|
||
'cost': cost,
|
||
'average': average,
|
||
'filled': filled,
|
||
'remaining': None,
|
||
'status': status,
|
||
'fee': fee,
|
||
'trades': fills,
|
||
}, market)
|
||
|
||
def create_orders(self, orders: List[OrderRequest], params={}):
|
||
"""
|
||
*contract only* create a list of trade orders
|
||
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Place-Multiple-Orders
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Place-Multiple-Orders
|
||
https://developers.binance.com/docs/derivatives/option/trade/Place-Multiple-Orders
|
||
|
||
: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
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
ordersRequests = []
|
||
orderSymbols = []
|
||
for i in range(0, len(orders)):
|
||
rawOrder = orders[i]
|
||
marketId = self.safe_string(rawOrder, 'symbol')
|
||
orderSymbols.append(marketId)
|
||
type = self.safe_string(rawOrder, 'type')
|
||
side = self.safe_string(rawOrder, 'side')
|
||
amount = self.safe_value(rawOrder, 'amount')
|
||
price = self.safe_value(rawOrder, 'price')
|
||
orderParams = self.safe_dict(rawOrder, 'params', {})
|
||
orderRequest = self.create_order_request(marketId, type, side, amount, price, orderParams)
|
||
ordersRequests.append(orderRequest)
|
||
orderSymbols = self.market_symbols(orderSymbols, None, False, True, True)
|
||
market = self.market(orderSymbols[0])
|
||
if market['spot']:
|
||
raise NotSupported(self.id + ' createOrders() does not support ' + market['type'] + ' orders')
|
||
response = None
|
||
request: dict = {
|
||
'batchOrders': ordersRequests,
|
||
}
|
||
request = self.extend(request, params)
|
||
if market['linear']:
|
||
response = self.fapiPrivatePostBatchOrders(request)
|
||
elif market['option']:
|
||
response = self.eapiPrivatePostBatchOrders(request)
|
||
else:
|
||
response = self.dapiPrivatePostBatchOrders(request)
|
||
#
|
||
# [
|
||
# {
|
||
# "code": -4005,
|
||
# "msg": "Quantity greater than max quantity."
|
||
# },
|
||
# {
|
||
# "orderId": 650640530,
|
||
# "symbol": "LTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcu32184eb13585491289bbaf",
|
||
# "price": "54.00",
|
||
# "avgPrice": "0.00",
|
||
# "origQty": "0.100",
|
||
# "executedQty": "0.000",
|
||
# "cumQty": "0.000",
|
||
# "cumQuote": "0.00000",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0.00",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "priceMatch": "NONE",
|
||
# "selfTradePreventionMode": "NONE",
|
||
# "goodTillDate": 0,
|
||
# "updateTime": 1698073926929
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_orders(response)
|
||
|
||
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
create a trade order
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
|
||
https://developers.binance.com/docs/binance-spot-api-docs/testnet/rest-api/trading-endpoints#test-new-order-trade
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/New-Order
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api
|
||
https://developers.binance.com/docs/derivatives/option/trade/New-Order
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#sor
|
||
https://developers.binance.com/docs/binance-spot-api-docs/testnet/rest-api/trading-endpoints#sor
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-UM-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-Margin-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-UM-Conditional-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Conditional-Order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: 'market' or 'limit' or 'STOP_LOSS' or 'STOP_LOSS_LIMIT' or 'TAKE_PROFIT' or 'TAKE_PROFIT_LIMIT' or 'STOP'
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much of you want to trade in units of the base currency
|
||
:param float [price]: the price that 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.reduceOnly]: for swap and future reduceOnly is a string 'true' or 'false' that cant be sent with close position set to True or in hedge mode. For spot margin and option reduceOnly is a boolean.
|
||
:param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
|
||
:param boolean [params.sor]: *spot only* whether to use SOR(Smart Order Routing) or not, default is False
|
||
:param boolean [params.test]: *spot only* whether to use the test endpoint or not, default is False
|
||
:param float [params.trailingPercent]: the percent to trail away from the current market price
|
||
:param float [params.trailingTriggerPrice]: the price to trigger a trailing order, default uses the price argument
|
||
:param float [params.triggerPrice]: the price that a trigger order is triggered at
|
||
:param float [params.stopLossPrice]: the price that a stop loss order is triggered at
|
||
:param float [params.takeProfitPrice]: the price that a take profit order is triggered at
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to create an order in a portfolio margin account
|
||
:param str [params.selfTradePrevention]: set unified value for stp, one of NONE, EXPIRE_MAKER, EXPIRE_TAKER or EXPIRE_BOTH
|
||
:param float [params.icebergAmount]: set iceberg amount for limit orders
|
||
:param str [params.stopLossOrTakeProfit]: 'stopLoss' or 'takeProfit', required for spot trailing orders
|
||
:param str [params.positionSide]: *swap and portfolio margin only* "BOTH" for one-way mode, "LONG" for buy side of hedged mode, "SHORT" for sell side of hedged mode
|
||
:param bool [params.hedged]: *swap and portfolio margin only* True for hedged mode, False for one way mode, default is False
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
# don't handle/omit params here, omitting happens inside createOrderRequest
|
||
marketType = self.safe_string(params, 'type', market['type'])
|
||
marginMode = self.safe_string(params, 'marginMode')
|
||
porfolioOptionsValue = self.safe_bool_2(self.options, 'papi', 'portfolioMargin', False)
|
||
isPortfolioMargin = self.safe_bool_2(params, 'papi', 'portfolioMargin', porfolioOptionsValue)
|
||
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
||
stopLossPrice = self.safe_string(params, 'stopLossPrice')
|
||
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
|
||
trailingPercent = self.safe_string_2(params, 'trailingPercent', 'callbackRate')
|
||
isTrailingPercentOrder = trailingPercent is not None
|
||
isStopLoss = stopLossPrice is not None
|
||
isTakeProfit = takeProfitPrice is not None
|
||
isConditional = (triggerPrice is not None) or isTrailingPercentOrder or isStopLoss or isTakeProfit
|
||
sor = self.safe_bool_2(params, 'sor', 'SOR', False)
|
||
test = self.safe_bool(params, 'test', False)
|
||
params = self.omit(params, ['sor', 'SOR', 'test'])
|
||
# if isPortfolioMargin:
|
||
# params['portfolioMargin'] = isPortfolioMargin
|
||
# }
|
||
request = self.create_order_request(symbol, type, side, amount, price, params)
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPrivatePostOrder(request)
|
||
elif sor:
|
||
if test:
|
||
response = self.privatePostSorOrderTest(request)
|
||
else:
|
||
response = self.privatePostSorOrder(request)
|
||
elif market['linear']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiPostUmConditionalOrder(request)
|
||
else:
|
||
response = self.papiPostUmOrder(request)
|
||
else:
|
||
response = self.fapiPrivatePostOrder(request)
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiPostCmConditionalOrder(request)
|
||
else:
|
||
response = self.papiPostCmOrder(request)
|
||
else:
|
||
response = self.dapiPrivatePostOrder(request)
|
||
elif marketType == 'margin' or marginMode is not None or isPortfolioMargin:
|
||
if isPortfolioMargin:
|
||
response = self.papiPostMarginOrder(request)
|
||
else:
|
||
response = self.sapiPostMarginOrder(request)
|
||
else:
|
||
if test:
|
||
response = self.privatePostOrderTest(request)
|
||
else:
|
||
response = self.privatePostOrder(request)
|
||
return self.parse_order(response, market)
|
||
|
||
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
@ignore
|
||
helper function to build the 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 that 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)
|
||
marketType = self.safe_string(params, 'type', market['type'])
|
||
clientOrderId = self.safe_string_2(params, 'newClientOrderId', 'clientOrderId')
|
||
initialUppercaseType = type.upper()
|
||
isMarketOrder = initialUppercaseType == 'MARKET'
|
||
isLimitOrder = initialUppercaseType == 'LIMIT'
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'side': side.upper(),
|
||
}
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'createOrder', 'papi', 'portfolioMargin', False)
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('createOrder', params)
|
||
reduceOnly = self.safe_bool(params, 'reduceOnly', False)
|
||
if reduceOnly:
|
||
if marketType == 'margin' or (not market['contract'] and (marginMode is not None)):
|
||
params = self.omit(params, 'reduceOnly')
|
||
request['sideEffectType'] = 'AUTO_REPAY'
|
||
triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
|
||
stopLossPrice = self.safe_string(params, 'stopLossPrice', triggerPrice) # fallback to stopLoss
|
||
takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
|
||
trailingDelta = self.safe_string(params, 'trailingDelta')
|
||
trailingTriggerPrice = self.safe_string_2(params, 'trailingTriggerPrice', 'activationPrice')
|
||
trailingPercent = self.safe_string_n(params, ['trailingPercent', 'callbackRate', 'trailingDelta'])
|
||
priceMatch = self.safe_string(params, 'priceMatch')
|
||
isTrailingPercentOrder = trailingPercent is not None
|
||
isStopLoss = stopLossPrice is not None or trailingDelta is not None
|
||
isTakeProfit = takeProfitPrice is not None
|
||
isTriggerOrder = triggerPrice is not None
|
||
isConditional = isTriggerOrder or isTrailingPercentOrder or isStopLoss or isTakeProfit
|
||
isPortfolioMarginConditional = (isPortfolioMargin and isConditional)
|
||
isPriceMatch = priceMatch is not None
|
||
priceRequiredForTrailing = True
|
||
uppercaseType = type.upper()
|
||
stopPrice = None
|
||
if isTrailingPercentOrder:
|
||
if market['swap']:
|
||
uppercaseType = 'TRAILING_STOP_MARKET'
|
||
request['callbackRate'] = trailingPercent
|
||
if trailingTriggerPrice is not None:
|
||
request['activationPrice'] = self.price_to_precision(symbol, trailingTriggerPrice)
|
||
else:
|
||
if (uppercaseType != 'STOP_LOSS') and (uppercaseType != 'TAKE_PROFIT') and (uppercaseType != 'STOP_LOSS_LIMIT') and (uppercaseType != 'TAKE_PROFIT_LIMIT'):
|
||
stopLossOrTakeProfit = self.safe_string(params, 'stopLossOrTakeProfit')
|
||
params = self.omit(params, 'stopLossOrTakeProfit')
|
||
if (stopLossOrTakeProfit != 'stopLoss') and (stopLossOrTakeProfit != 'takeProfit'):
|
||
raise InvalidOrder(self.id + symbol + ' trailingPercent orders require a stopLossOrTakeProfit parameter of either stopLoss or takeProfit')
|
||
if isMarketOrder:
|
||
if stopLossOrTakeProfit == 'stopLoss':
|
||
uppercaseType = 'STOP_LOSS'
|
||
elif stopLossOrTakeProfit == 'takeProfit':
|
||
uppercaseType = 'TAKE_PROFIT'
|
||
else:
|
||
if stopLossOrTakeProfit == 'stopLoss':
|
||
uppercaseType = 'STOP_LOSS_LIMIT'
|
||
elif stopLossOrTakeProfit == 'takeProfit':
|
||
uppercaseType = 'TAKE_PROFIT_LIMIT'
|
||
if (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT'):
|
||
priceRequiredForTrailing = False
|
||
if trailingTriggerPrice is not None:
|
||
stopPrice = self.price_to_precision(symbol, trailingTriggerPrice)
|
||
trailingPercentConverted = Precise.string_mul(trailingPercent, '100')
|
||
request['trailingDelta'] = trailingPercentConverted
|
||
elif isStopLoss:
|
||
stopPrice = stopLossPrice
|
||
if isMarketOrder:
|
||
# spot STOP_LOSS market orders are not a valid order type
|
||
uppercaseType = 'STOP_MARKET' if market['contract'] else 'STOP_LOSS'
|
||
elif isLimitOrder:
|
||
uppercaseType = 'STOP' if market['contract'] else 'STOP_LOSS_LIMIT'
|
||
elif isTakeProfit:
|
||
stopPrice = takeProfitPrice
|
||
if isMarketOrder:
|
||
# spot TAKE_PROFIT market orders are not a valid order type
|
||
uppercaseType = 'TAKE_PROFIT_MARKET' if market['contract'] else 'TAKE_PROFIT'
|
||
elif isLimitOrder:
|
||
uppercaseType = 'TAKE_PROFIT' if market['contract'] else 'TAKE_PROFIT_LIMIT'
|
||
if market['option']:
|
||
if type == 'market':
|
||
raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type for the ' + symbol + ' market')
|
||
else:
|
||
validOrderTypes = self.safe_list(market['info'], 'orderTypes')
|
||
if not self.in_array(uppercaseType, validOrderTypes):
|
||
if initialUppercaseType != uppercaseType:
|
||
raise InvalidOrder(self.id + ' triggerPrice parameter is not allowed for ' + symbol + ' ' + type + ' orders')
|
||
else:
|
||
raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type for the ' + symbol + ' market')
|
||
clientOrderIdRequest = 'newClientStrategyId' if isPortfolioMarginConditional else 'newClientOrderId'
|
||
if clientOrderId is None:
|
||
broker = self.safe_dict(self.options, 'broker', {})
|
||
defaultId = 'x-xcKtGhcu' if (market['contract']) else 'x-TKT5PX2F'
|
||
idMarketType = 'spot'
|
||
if market['contract']:
|
||
idMarketType = 'swap' if (market['swap'] and market['linear']) else 'inverse'
|
||
brokerId = self.safe_string(broker, idMarketType, defaultId)
|
||
request[clientOrderIdRequest] = brokerId + self.uuid22()
|
||
else:
|
||
request[clientOrderIdRequest] = clientOrderId
|
||
postOnly = None
|
||
if not isPortfolioMargin:
|
||
postOnly = self.is_post_only(isMarketOrder, initialUppercaseType == 'LIMIT_MAKER', params)
|
||
if market['spot'] or marketType == 'margin':
|
||
# only supported for spot/margin api(all margin markets are spot markets)
|
||
if postOnly:
|
||
uppercaseType = 'LIMIT_MAKER'
|
||
if marginMode == 'isolated':
|
||
request['isIsolated'] = True
|
||
else:
|
||
postOnly = self.is_post_only(isMarketOrder, initialUppercaseType == 'LIMIT_MAKER', params)
|
||
if postOnly:
|
||
if not market['contract']:
|
||
uppercaseType = 'LIMIT_MAKER'
|
||
else:
|
||
request['timeInForce'] = 'GTX'
|
||
# handle newOrderRespType response type
|
||
if ((marketType == 'spot') or (marketType == 'margin')) and not isPortfolioMargin:
|
||
request['newOrderRespType'] = self.safe_string(self.options['newOrderRespType'], type, 'FULL') # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
|
||
else:
|
||
# swap, futures and options
|
||
request['newOrderRespType'] = 'RESULT' # "ACK", "RESULT", default "ACK"
|
||
typeRequest = 'strategyType' if isPortfolioMarginConditional else 'type'
|
||
request[typeRequest] = uppercaseType
|
||
# additional required fields depending on the order type
|
||
closePosition = self.safe_bool(params, 'closePosition', False)
|
||
timeInForceIsRequired = False
|
||
priceIsRequired = False
|
||
triggerPriceIsRequired = False
|
||
quantityIsRequired = False
|
||
#
|
||
# spot/margin
|
||
#
|
||
# LIMIT timeInForce, quantity, price
|
||
# MARKET quantity or quoteOrderQty
|
||
# STOP_LOSS quantity, stopPrice
|
||
# STOP_LOSS_LIMIT timeInForce, quantity, price, stopPrice
|
||
# TAKE_PROFIT quantity, stopPrice
|
||
# TAKE_PROFIT_LIMIT timeInForce, quantity, price, stopPrice
|
||
# LIMIT_MAKER quantity, price
|
||
#
|
||
# futures
|
||
#
|
||
# LIMIT timeInForce, quantity, price
|
||
# MARKET quantity
|
||
# STOP/TAKE_PROFIT quantity, price, stopPrice
|
||
# STOP_MARKET stopPrice
|
||
# TAKE_PROFIT_MARKET stopPrice
|
||
# TRAILING_STOP_MARKET callbackRate
|
||
#
|
||
if uppercaseType == 'MARKET':
|
||
if market['spot']:
|
||
quoteOrderQty = self.safe_bool(self.options, 'quoteOrderQty', True)
|
||
if quoteOrderQty:
|
||
quoteOrderQtyNew = self.safe_string_2(params, 'quoteOrderQty', 'cost')
|
||
precision = market['precision']['price']
|
||
if quoteOrderQtyNew is not None:
|
||
request['quoteOrderQty'] = self.decimal_to_precision(quoteOrderQtyNew, TRUNCATE, precision, self.precisionMode)
|
||
elif price is not None:
|
||
amountString = self.number_to_string(amount)
|
||
priceString = self.number_to_string(price)
|
||
quoteOrderQuantity = Precise.string_mul(amountString, priceString)
|
||
request['quoteOrderQty'] = self.decimal_to_precision(quoteOrderQuantity, TRUNCATE, precision, self.precisionMode)
|
||
else:
|
||
quantityIsRequired = True
|
||
else:
|
||
quantityIsRequired = True
|
||
else:
|
||
quantityIsRequired = True
|
||
elif uppercaseType == 'LIMIT':
|
||
priceIsRequired = True
|
||
timeInForceIsRequired = True
|
||
quantityIsRequired = True
|
||
elif (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT'):
|
||
triggerPriceIsRequired = True
|
||
quantityIsRequired = True
|
||
if (market['linear'] or market['inverse']) and priceRequiredForTrailing:
|
||
priceIsRequired = True
|
||
elif (uppercaseType == 'STOP_LOSS_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
|
||
quantityIsRequired = True
|
||
triggerPriceIsRequired = True
|
||
priceIsRequired = True
|
||
timeInForceIsRequired = True
|
||
elif uppercaseType == 'LIMIT_MAKER':
|
||
priceIsRequired = True
|
||
quantityIsRequired = True
|
||
elif uppercaseType == 'STOP':
|
||
quantityIsRequired = True
|
||
triggerPriceIsRequired = True
|
||
priceIsRequired = True
|
||
elif (uppercaseType == 'STOP_MARKET') or (uppercaseType == 'TAKE_PROFIT_MARKET'):
|
||
if not closePosition:
|
||
quantityIsRequired = True
|
||
triggerPriceIsRequired = True
|
||
elif uppercaseType == 'TRAILING_STOP_MARKET':
|
||
if not closePosition:
|
||
quantityIsRequired = True
|
||
if trailingPercent is None:
|
||
raise InvalidOrder(self.id + ' createOrder() requires a trailingPercent param for a ' + type + ' order')
|
||
if quantityIsRequired:
|
||
marketAmountPrecision = self.safe_string(market['precision'], 'amount')
|
||
isPrecisionAvailable = (marketAmountPrecision is not None)
|
||
if isPrecisionAvailable:
|
||
request['quantity'] = self.amount_to_precision(symbol, amount)
|
||
else:
|
||
request['quantity'] = self.parse_to_numeric(amount) # some options don't have the precision available
|
||
if priceIsRequired and not isPriceMatch:
|
||
if price is None:
|
||
raise InvalidOrder(self.id + ' createOrder() requires a price argument for a ' + type + ' order')
|
||
pricePrecision = self.safe_string(market['precision'], 'price')
|
||
isPricePrecisionAvailable = (pricePrecision is not None)
|
||
if isPricePrecisionAvailable:
|
||
request['price'] = self.price_to_precision(symbol, price)
|
||
else:
|
||
request['price'] = self.parse_to_numeric(price) # some options don't have the precision available
|
||
if triggerPriceIsRequired:
|
||
if market['contract']:
|
||
if stopPrice is None:
|
||
raise InvalidOrder(self.id + ' createOrder() requires a triggerPrice extra param for a ' + type + ' order')
|
||
else:
|
||
# check for delta price
|
||
if trailingDelta is None and stopPrice is None and trailingPercent is None:
|
||
raise InvalidOrder(self.id + ' createOrder() requires a triggerPrice, trailingDelta or trailingPercent param for a ' + type + ' order')
|
||
if stopPrice is not None:
|
||
request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
|
||
if timeInForceIsRequired and (self.safe_string(params, 'timeInForce') is None) and (self.safe_string(request, 'timeInForce') is None):
|
||
request['timeInForce'] = self.safe_string(self.options, 'defaultTimeInForce') # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
|
||
if not isPortfolioMargin and market['contract'] and postOnly:
|
||
request['timeInForce'] = 'GTX'
|
||
# remove timeInForce from params because PO is only used by self.is_post_only and it's not a valid value for Binance
|
||
if self.safe_string(params, 'timeInForce') == 'PO':
|
||
params = self.omit(params, 'timeInForce')
|
||
hedged = self.safe_bool(params, 'hedged', False)
|
||
if not market['spot'] and not market['option'] and hedged:
|
||
if reduceOnly:
|
||
params = self.omit(params, 'reduceOnly')
|
||
side = 'sell' if (side == 'buy') else 'buy'
|
||
request['positionSide'] = 'LONG' if (side == 'buy') else 'SHORT'
|
||
# unified stp
|
||
selfTradePrevention = None
|
||
selfTradePrevention, params = self.handle_option_and_params(params, 'createOrder', 'selfTradePrevention')
|
||
if selfTradePrevention is not None:
|
||
if market['spot']:
|
||
request['selfTradePreventionMode'] = selfTradePrevention.upper() # binance enums exactly match the unified ccxt enums(but needs uppercase)
|
||
# unified iceberg
|
||
icebergAmount = self.safe_number(params, 'icebergAmount')
|
||
if icebergAmount is not None:
|
||
if market['spot']:
|
||
request['icebergQty'] = self.amount_to_precision(symbol, icebergAmount)
|
||
requestParams = self.omit(params, ['type', 'newClientOrderId', 'clientOrderId', 'postOnly', 'stopLossPrice', 'takeProfitPrice', 'stopPrice', 'triggerPrice', 'trailingTriggerPrice', 'trailingPercent', 'quoteOrderQty', 'cost', 'test', 'hedged', 'icebergAmount'])
|
||
return self.extend(request, requestParams)
|
||
|
||
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
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
|
||
|
||
: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>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' createMarketOrderWithCost() supports spot orders only')
|
||
req = {
|
||
'cost': cost,
|
||
}
|
||
return self.create_order(symbol, 'market', side, cost, None, self.extend(req, 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
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only')
|
||
req = {
|
||
'cost': cost,
|
||
}
|
||
return self.create_order(symbol, 'market', 'buy', cost, None, self.extend(req, 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
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#new-order-trade
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param float cost: how much you want to trade in units of the quote currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['spot']:
|
||
raise NotSupported(self.id + ' createMarketSellOrderWithCost() supports spot orders only')
|
||
params['quoteOrderQty'] = cost
|
||
return self.create_order(symbol, 'market', 'sell', cost, None, params)
|
||
|
||
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
fetches information on an order made by the user
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#query-order-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Query-Order
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Query-Order
|
||
https://developers.binance.com/docs/derivatives/option/trade/Query-Single-Order
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order
|
||
|
||
: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 str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch an order in a portfolio margin account
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
defaultType = self.safe_string_2(self.options, 'fetchOrder', 'defaultType', 'spot')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchOrder', params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchOrder', 'papi', 'portfolioMargin', False)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
clientOrderId = self.safe_string_2(params, 'origClientOrderId', 'clientOrderId')
|
||
if clientOrderId is not None:
|
||
if market['option']:
|
||
request['clientOrderId'] = clientOrderId
|
||
else:
|
||
request['origClientOrderId'] = clientOrderId
|
||
else:
|
||
request['orderId'] = id
|
||
params = self.omit(params, ['type', 'clientOrderId', 'origClientOrderId'])
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPrivateGetOrder(self.extend(request, params))
|
||
elif market['linear']:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmOrder(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetOrder(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmOrder(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetOrder(self.extend(request, params))
|
||
elif (type == 'margin') or (marginMode is not None) or isPortfolioMargin:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetMarginOrder(self.extend(request, params))
|
||
else:
|
||
if marginMode == 'isolated':
|
||
request['isIsolated'] = True
|
||
response = self.sapiGetMarginOrder(self.extend(request, params))
|
||
else:
|
||
response = self.privateGetOrder(self.extend(request, params))
|
||
return self.parse_order(response, 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://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#all-orders-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/option/trade/Query-Option-Order-History
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-All-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders
|
||
|
||
: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 str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
|
||
:param int [params.until]: the latest time in ms to fetch orders for
|
||
: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)
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch orders in a portfolio margin account
|
||
:param boolean [params.trigger]: set to True if you would like to fetch portfolio margin account trigger or conditional orders
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_dynamic('fetchOrders', symbol, since, limit, params)
|
||
market = self.market(symbol)
|
||
defaultType = self.safe_string_2(self.options, 'fetchOrders', 'defaultType', market['type'])
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchOrders', params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchOrders', 'papi', 'portfolioMargin', False)
|
||
isConditional = self.safe_bool_n(params, ['stop', 'trigger', 'conditional'])
|
||
params = self.omit(params, ['stop', 'trigger', 'conditional', 'type'])
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPrivateGetHistoryOrders(self.extend(request, params))
|
||
elif market['linear']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiGetUmConditionalAllOrders(self.extend(request, params))
|
||
else:
|
||
response = self.papiGetUmAllOrders(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetAllOrders(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiGetCmConditionalAllOrders(self.extend(request, params))
|
||
else:
|
||
response = self.papiGetCmAllOrders(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetAllOrders(self.extend(request, params))
|
||
else:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetMarginAllOrders(self.extend(request, params))
|
||
elif type == 'margin' or marginMode is not None:
|
||
if marginMode == 'isolated':
|
||
request['isIsolated'] = True
|
||
response = self.sapiGetMarginAllOrders(self.extend(request, params))
|
||
else:
|
||
response = self.privateGetAllOrders(self.extend(request, params))
|
||
#
|
||
# spot
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "LTCBTC",
|
||
# "orderId": 1,
|
||
# "clientOrderId": "myOrder1",
|
||
# "price": "0.1",
|
||
# "origQty": "1.0",
|
||
# "executedQty": "0.0",
|
||
# "cummulativeQuoteQty": "0.0",
|
||
# "status": "NEW",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "stopPrice": "0.0",
|
||
# "icebergQty": "0.0",
|
||
# "time": 1499827319559,
|
||
# "updateTime": 1499827319559,
|
||
# "isWorking": True
|
||
# }
|
||
# ]
|
||
#
|
||
# futures
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": 1,
|
||
# "clientOrderId": "myOrder1",
|
||
# "price": "0.1",
|
||
# "origQty": "1.0",
|
||
# "executedQty": "1.0",
|
||
# "cumQuote": "10.0",
|
||
# "status": "NEW",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "stopPrice": "0.0",
|
||
# "updateTime": 1499827319559
|
||
# }
|
||
# ]
|
||
#
|
||
# options
|
||
#
|
||
# [
|
||
# {
|
||
# "orderId": 4728833085436977152,
|
||
# "symbol": "ETH-230211-1500-C",
|
||
# "price": "10.0",
|
||
# "quantity": "1.00",
|
||
# "executedQty": "0.00",
|
||
# "fee": "0",
|
||
# "side": "BUY",
|
||
# "type": "LIMIT",
|
||
# "timeInForce": "GTC",
|
||
# "reduceOnly": False,
|
||
# "postOnly": False,
|
||
# "createTime": 1676083034462,
|
||
# "updateTime": 1676083034462,
|
||
# "status": "ACCEPTED",
|
||
# "avgPrice": "0",
|
||
# "source": "API",
|
||
# "clientOrderId": "",
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "quoteAsset": "USDT",
|
||
# "lastTrade": {"id":"69","time":"1676084430567","price":"24.9","qty":"1.00"},
|
||
# "mmp": False
|
||
# }
|
||
# ]
|
||
#
|
||
# inverse portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "orderId": 71328442983,
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "pair": "ETHUSD",
|
||
# "status": "CANCELED",
|
||
# "clientOrderId": "x-xcKtGhcu4b3e3d8515dd4dc5ba9ccc",
|
||
# "price": "2000",
|
||
# "avgPrice": "0.00",
|
||
# "origQty": "1",
|
||
# "executedQty": "0",
|
||
# "cumBase": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "origType": "LIMIT",
|
||
# "time": 1707197843046,
|
||
# "updateTime": 1707197941373,
|
||
# "positionSide": "BOTH"
|
||
# },
|
||
# ]
|
||
#
|
||
# linear portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "orderId": 259235347005,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "CANCELED",
|
||
# "clientOrderId": "x-xcKtGhcu402881c9103f42bdb4183b",
|
||
# "price": "35000",
|
||
# "avgPrice": "0.00000",
|
||
# "origQty": "0.010",
|
||
# "executedQty": "0",
|
||
# "cumQuote": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "origType": "LIMIT",
|
||
# "time": 1707194702167,
|
||
# "updateTime": 1707197804748,
|
||
# "positionSide": "BOTH",
|
||
# "selfTradePreventionMode": "NONE",
|
||
# "goodTillDate": 0
|
||
# },
|
||
# ]
|
||
#
|
||
# conditional portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "newClientStrategyId": "x-xcKtGhcuaf166172ed504cd1bc0396",
|
||
# "strategyId": 3733211,
|
||
# "strategyStatus": "CANCELLED",
|
||
# "strategyType": "STOP",
|
||
# "origQty": "0.010",
|
||
# "price": "35000",
|
||
# "orderId": 0,
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "50000",
|
||
# "symbol": "BTCUSDT",
|
||
# "type": "LIMIT",
|
||
# "bookTime": 1707270098774,
|
||
# "updateTime": 1707270119261,
|
||
# "timeInForce": "GTC",
|
||
# "triggerTime": 0,
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "goodTillDate": 0,
|
||
# "selfTradePreventionMode": "NONE"
|
||
# },
|
||
# ]
|
||
#
|
||
# spot margin portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "orderId": 24684460474,
|
||
# "clientOrderId": "x-TKT5PX2Fe9ef29d8346440f0b28b86",
|
||
# "price": "35000.00000000",
|
||
# "origQty": "0.00100000",
|
||
# "executedQty": "0.00000000",
|
||
# "cummulativeQuoteQty": "0.00000000",
|
||
# "status": "CANCELED",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "stopPrice": "0.00000000",
|
||
# "icebergQty": "0.00000000",
|
||
# "time": 1707113538870,
|
||
# "updateTime": 1707113797688,
|
||
# "isWorking": True,
|
||
# "accountId": 200180970,
|
||
# "selfTradePreventionMode": "EXPIRE_MAKER",
|
||
# "preventedMatchId": null,
|
||
# "preventedQuantity": null
|
||
# },
|
||
# ]
|
||
#
|
||
return self.parse_orders(response, market, since, limit)
|
||
|
||
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetch all unfilled currently open orders
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#current-open-orders-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Current-All-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Current-All-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/option/trade/Query-Current-Open-Option-Orders
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Conditional-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Conditional-Orders
|
||
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch open orders for
|
||
:param int [limit]: the maximum number of open orders structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch open orders in the portfolio margin account
|
||
:param boolean [params.trigger]: set to True if you would like to fetch portfolio margin account conditional orders
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
type = None
|
||
request: dict = {}
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchOpenOrders', params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchOpenOrders', 'papi', 'portfolioMargin', False)
|
||
isConditional = self.safe_bool_n(params, ['stop', 'trigger', 'conditional'])
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', 'spot')
|
||
marketType = market['type'] if ('type' in market) else defaultType
|
||
type = self.safe_string(params, 'type', marketType)
|
||
elif self.options['warnOnFetchOpenOrdersWithoutSymbol']:
|
||
raise ExchangeError(self.id + ' fetchOpenOrders() WARNING: fetching open orders without specifying a symbol has stricter rate limits(10 times more for spot, 40 times more for other markets) compared to requesting with symbol argument. To acknowledge self warning, set ' + self.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = False to suppress self warning message.')
|
||
else:
|
||
defaultType = self.safe_string_2(self.options, 'fetchOpenOrders', 'defaultType', 'spot')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchOpenOrders', market, params)
|
||
params = self.omit(params, ['type', 'stop', 'trigger', 'conditional'])
|
||
response = None
|
||
if type == 'option':
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.eapiPrivateGetOpenOrders(self.extend(request, params))
|
||
elif self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiGetUmConditionalOpenOrders(self.extend(request, params))
|
||
else:
|
||
response = self.papiGetUmOpenOrders(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetOpenOrders(self.extend(request, params))
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiGetCmConditionalOpenOrders(self.extend(request, params))
|
||
else:
|
||
response = self.papiGetCmOpenOrders(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetOpenOrders(self.extend(request, params))
|
||
elif type == 'margin' or marginMode is not None or isPortfolioMargin:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetMarginOpenOrders(self.extend(request, params))
|
||
else:
|
||
if marginMode == 'isolated':
|
||
request['isIsolated'] = True
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument for isolated markets')
|
||
response = self.sapiGetMarginOpenOrders(self.extend(request, params))
|
||
else:
|
||
response = self.privateGetOpenOrders(self.extend(request, params))
|
||
return self.parse_orders(response, market, since, limit)
|
||
|
||
def fetch_open_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
fetch an open order by the id
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Query-Current-Open-Order
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Query-Current-Open-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Conditional-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Conditional-Order
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.trigger]: set to True if you would like to fetch portfolio margin account stop or conditional orders
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch for a portfolio margin account
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOpenOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchOpenOrder', 'papi', 'portfolioMargin', False)
|
||
isConditional = self.safe_bool_n(params, ['stop', 'trigger', 'conditional'])
|
||
params = self.omit(params, ['stop', 'trigger', 'conditional'])
|
||
isPortfolioMarginConditional = (isPortfolioMargin and isConditional)
|
||
orderIdRequest = 'strategyId' if isPortfolioMarginConditional else 'orderId'
|
||
request[orderIdRequest] = id
|
||
response = None
|
||
if market['linear']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiGetUmConditionalOpenOrder(self.extend(request, params))
|
||
else:
|
||
response = self.papiGetUmOpenOrder(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetOpenOrder(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiGetCmConditionalOpenOrder(self.extend(request, params))
|
||
else:
|
||
response = self.papiGetCmOpenOrder(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetOpenOrder(self.extend(request, params))
|
||
else:
|
||
if market['option']:
|
||
raise NotSupported(self.id + ' fetchOpenOrder() does not support option markets')
|
||
elif market['spot']:
|
||
raise NotSupported(self.id + ' fetchOpenOrder() does not support spot markets')
|
||
#
|
||
# linear swap
|
||
#
|
||
# {
|
||
# "orderId": 3697213934,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcufb20c5a7761a4aa09aa156",
|
||
# "price": "33000.00",
|
||
# "avgPrice": "0.00000",
|
||
# "origQty": "0.010",
|
||
# "executedQty": "0.000",
|
||
# "cumQuote": "0.00000",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0.00",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "priceMatch": "NONE",
|
||
# "selfTradePreventionMode": "NONE",
|
||
# "goodTillDate": 0,
|
||
# "time": 1707892893502,
|
||
# "updateTime": 1707892893515
|
||
# }
|
||
#
|
||
# inverse swap
|
||
#
|
||
# {
|
||
# "orderId": 597368542,
|
||
# "symbol": "BTCUSD_PERP",
|
||
# "pair": "BTCUSD",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcubbde7ba93b1a4ab881eff3",
|
||
# "price": "35000",
|
||
# "avgPrice": "0",
|
||
# "origQty": "1",
|
||
# "executedQty": "0",
|
||
# "cumBase": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "time": 1707893453199,
|
||
# "updateTime": 1707893453199
|
||
# }
|
||
#
|
||
# linear portfolio margin
|
||
#
|
||
# {
|
||
# "orderId": 264895013409,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcu6278f1adbdf14f74ab432e",
|
||
# "price": "35000",
|
||
# "avgPrice": "0",
|
||
# "origQty": "0.010",
|
||
# "executedQty": "0",
|
||
# "cumQuote": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "LONG",
|
||
# "origType": "LIMIT",
|
||
# "time": 1707893839364,
|
||
# "updateTime": 1707893839364,
|
||
# "goodTillDate": 0,
|
||
# "selfTradePreventionMode": "NONE"
|
||
# }
|
||
#
|
||
# inverse portfolio margin
|
||
#
|
||
# {
|
||
# "orderId": 71790316950,
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "pair": "ETHUSD",
|
||
# "status": "NEW",
|
||
# "clientOrderId": "x-xcKtGhcuec11030474204ab08ba2c2",
|
||
# "price": "2500",
|
||
# "avgPrice": "0",
|
||
# "origQty": "1",
|
||
# "executedQty": "0",
|
||
# "cumBase": "0",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "LONG",
|
||
# "origType": "LIMIT",
|
||
# "time": 1707894181694,
|
||
# "updateTime": 1707894181694
|
||
# }
|
||
#
|
||
# linear portfolio margin conditional
|
||
#
|
||
# {
|
||
# "newClientStrategyId": "x-xcKtGhcu2205fde44418483ca21874",
|
||
# "strategyId": 4084339,
|
||
# "strategyStatus": "NEW",
|
||
# "strategyType": "STOP",
|
||
# "origQty": "0.010",
|
||
# "price": "35000",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "LONG",
|
||
# "stopPrice": "60000",
|
||
# "symbol": "BTCUSDT",
|
||
# "bookTime": 1707894490094,
|
||
# "updateTime": 1707894490094,
|
||
# "timeInForce": "GTC",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "goodTillDate": 0,
|
||
# "selfTradePreventionMode": "NONE"
|
||
# }
|
||
#
|
||
# inverse portfolio margin conditional
|
||
#
|
||
# {
|
||
# "newClientStrategyId": "x-xcKtGhcu2da9c765294b433994ffce",
|
||
# "strategyId": 1423501,
|
||
# "strategyStatus": "NEW",
|
||
# "strategyType": "STOP",
|
||
# "origQty": "1",
|
||
# "price": "2500",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "LONG",
|
||
# "stopPrice": "4000",
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "bookTime": 1707894782679,
|
||
# "updateTime": 1707894782679,
|
||
# "timeInForce": "GTC",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False
|
||
# }
|
||
#
|
||
return self.parse_order(response, market)
|
||
|
||
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple closed orders made by the user
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#all-orders-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/option/trade/Query-Option-Order-History
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-All-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders
|
||
|
||
: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 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)
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch orders in a portfolio margin account
|
||
:param boolean [params.trigger]: set to True if you would like to fetch portfolio margin account trigger or conditional orders
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
|
||
orders = self.fetch_orders(symbol, since, None, params)
|
||
filteredOrders = self.filter_by(orders, 'status', 'closed')
|
||
return self.filter_by_since_limit(filteredOrders, since, limit)
|
||
|
||
def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches information on multiple canceled orders made by the user
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#all-orders-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/option/trade/Query-Option-Order-History
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-All-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders
|
||
|
||
:param str symbol: unified market symbol of the market the 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 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)
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch orders in a portfolio margin account
|
||
:param boolean [params.trigger]: set to True if you would like to fetch portfolio margin account trigger or conditional orders
|
||
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchCanceledOrders() requires a symbol argument')
|
||
orders = self.fetch_orders(symbol, since, None, params)
|
||
filteredOrders = self.filter_by(orders, 'status', 'canceled')
|
||
return self.filter_by_since_limit(filteredOrders, since, limit)
|
||
|
||
def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple canceled orders made by the user
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#all-orders-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/All-Orders
|
||
https://developers.binance.com/docs/derivatives/option/trade/Query-Option-Order-History
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-All-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders
|
||
|
||
:param str symbol: unified market symbol of the market the 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 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)
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch orders in a portfolio margin account
|
||
:param boolean [params.trigger]: set to True if you would like to fetch portfolio margin account trigger or conditional orders
|
||
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchCanceledAndClosedOrders() requires a symbol argument')
|
||
orders = self.fetch_orders(symbol, since, None, params)
|
||
canceledOrders = self.filter_by(orders, 'status', 'canceled')
|
||
closedOrders = self.filter_by(orders, 'status', 'closed')
|
||
filteredOrders = self.array_concat(canceledOrders, closedOrders)
|
||
sortedOrders = self.sort_by(filteredOrders, 'timestamp')
|
||
return self.filter_by_since_limit(sortedOrders, since, limit)
|
||
|
||
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
cancels an open order
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-order-trade
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Cancel-Order
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Cancel-Order
|
||
https://developers.binance.com/docs/derivatives/option/trade/Cancel-Option-Order
|
||
https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Conditional-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Conditional-Order
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-Order
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified symbol of the market the order was made in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to cancel an order in a portfolio margin account
|
||
:param boolean [params.trigger]: set to True if you would like to cancel a portfolio margin account conditional order
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
defaultType = self.safe_string_2(self.options, 'cancelOrder', 'defaultType', 'spot')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('cancelOrder', params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'cancelOrder', 'papi', 'portfolioMargin', False)
|
||
isConditional = self.safe_bool_n(params, ['stop', 'trigger', 'conditional'])
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
clientOrderId = self.safe_string_n(params, ['origClientOrderId', 'clientOrderId', 'newClientStrategyId'])
|
||
if clientOrderId is not None:
|
||
if market['option']:
|
||
request['clientOrderId'] = clientOrderId
|
||
else:
|
||
if isPortfolioMargin and isConditional:
|
||
request['newClientStrategyId'] = clientOrderId
|
||
else:
|
||
request['origClientOrderId'] = clientOrderId
|
||
else:
|
||
if isPortfolioMargin and isConditional:
|
||
request['strategyId'] = id
|
||
else:
|
||
request['orderId'] = id
|
||
params = self.omit(params, ['type', 'origClientOrderId', 'clientOrderId', 'newClientStrategyId', 'stop', 'trigger', 'conditional'])
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPrivateDeleteOrder(self.extend(request, params))
|
||
elif market['linear']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiDeleteUmConditionalOrder(self.extend(request, params))
|
||
else:
|
||
response = self.papiDeleteUmOrder(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateDeleteOrder(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiDeleteCmConditionalOrder(self.extend(request, params))
|
||
else:
|
||
response = self.papiDeleteCmOrder(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateDeleteOrder(self.extend(request, params))
|
||
elif (type == 'margin') or (marginMode is not None) or isPortfolioMargin:
|
||
if isPortfolioMargin:
|
||
response = self.papiDeleteMarginOrder(self.extend(request, params))
|
||
else:
|
||
if marginMode == 'isolated':
|
||
request['isIsolated'] = True
|
||
response = self.sapiDeleteMarginOrder(self.extend(request, params))
|
||
else:
|
||
response = self.privateDeleteOrder(self.extend(request, params))
|
||
return self.parse_order(response, market)
|
||
|
||
def cancel_all_orders(self, symbol: Str = None, params={}):
|
||
"""
|
||
cancel all open orders in a market
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/trading-endpoints#cancel-all-open-orders-on-a-symbol-trade
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Cancel-All-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Cancel-All-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/option/trade/Cancel-all-Option-orders-on-specific-symbol
|
||
https://developers.binance.com/docs/margin_trading/trade/Margin-Account-Cancel-All-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Conditional-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Conditional-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-All-Open-Orders-on-a-Symbol
|
||
|
||
:param str symbol: unified market symbol of the market to cancel orders in
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to cancel orders in a portfolio margin account
|
||
:param boolean [params.trigger]: set to True if you would like to cancel portfolio margin account conditional orders
|
||
: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'],
|
||
}
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'cancelAllOrders', 'papi', 'portfolioMargin', False)
|
||
isConditional = self.safe_bool_n(params, ['stop', 'trigger', 'conditional'])
|
||
type = self.safe_string(params, 'type', market['type'])
|
||
params = self.omit(params, ['type', 'stop', 'trigger', 'conditional'])
|
||
marginMode = None
|
||
marginMode, params = self.handle_margin_mode_and_params('cancelAllOrders', params)
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPrivateDeleteAllOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": 0,
|
||
# "msg": "success"
|
||
# }
|
||
#
|
||
elif market['linear']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiDeleteUmConditionalAllOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200",
|
||
# "msg": "The operation of cancel all conditional open order is done."
|
||
# }
|
||
#
|
||
else:
|
||
response = self.papiDeleteUmAllOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "The operation of cancel all open order is done."
|
||
# }
|
||
#
|
||
else:
|
||
response = self.fapiPrivateDeleteAllOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "The operation of cancel all open order is done."
|
||
# }
|
||
#
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
if isConditional:
|
||
response = self.papiDeleteCmConditionalAllOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "200",
|
||
# "msg": "The operation of cancel all conditional open order is done."
|
||
# }
|
||
#
|
||
else:
|
||
response = self.papiDeleteCmAllOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "The operation of cancel all open order is done."
|
||
# }
|
||
#
|
||
else:
|
||
response = self.dapiPrivateDeleteAllOpenOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "The operation of cancel all open order is done."
|
||
# }
|
||
#
|
||
elif (type == 'margin') or (marginMode is not None) or isPortfolioMargin:
|
||
if isPortfolioMargin:
|
||
response = self.papiDeleteMarginAllOpenOrders(self.extend(request, params))
|
||
else:
|
||
if marginMode == 'isolated':
|
||
request['isIsolated'] = True
|
||
response = self.sapiDeleteMarginOpenOrders(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "isIsolated": True, # if isolated margin
|
||
# "origClientOrderId": "E6APeyTJvkMvLMYMqu1KQ4",
|
||
# "orderId": 11,
|
||
# "orderListId": -1,
|
||
# "clientOrderId": "pXLV6Hz6mprAcVYpVMTGgx",
|
||
# "price": "0.089853",
|
||
# "origQty": "0.178622",
|
||
# "executedQty": "0.000000",
|
||
# "cummulativeQuoteQty": "0.000000",
|
||
# "status": "CANCELED",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "selfTradePreventionMode": "NONE"
|
||
# },
|
||
# ...
|
||
# ]
|
||
#
|
||
else:
|
||
response = self.privateDeleteOpenOrders(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ADAUSDT",
|
||
# "origClientOrderId": "x-TKT5PX2F662cde7a90114475b86e21",
|
||
# "orderId": 3935107,
|
||
# "orderListId": -1,
|
||
# "clientOrderId": "bqM2w1oTlugfRAjnTIFBE8",
|
||
# "transactTime": 1720589016657,
|
||
# "price": "0.35000000",
|
||
# "origQty": "30.00000000",
|
||
# "executedQty": "0.00000000",
|
||
# "cummulativeQuoteQty": "0.00000000",
|
||
# "status": "CANCELED",
|
||
# "timeInForce": "GTC",
|
||
# "type": "LIMIT",
|
||
# "side": "BUY",
|
||
# "selfTradePreventionMode": "EXPIRE_MAKER"
|
||
# }
|
||
# ]
|
||
#
|
||
if isinstance(response, list):
|
||
return self.parse_orders(response, market)
|
||
else:
|
||
return [
|
||
self.safe_order({
|
||
'info': response,
|
||
}),
|
||
]
|
||
|
||
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
||
"""
|
||
cancel multiple orders
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Cancel-Multiple-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Cancel-Multiple-Orders
|
||
|
||
:param str[] ids: order ids
|
||
:param str [symbol]: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str[] [params.clientOrderIds]: alternative to ids, array of client order ids
|
||
|
||
EXCHANGE SPECIFIC PARAMETERS
|
||
:param str[] [params.origClientOrderIdList]: max length 10 e.g. ["my_id_1","my_id_2"], encode the double quotes. No space after comma
|
||
:param int[] [params.recvWindow]:
|
||
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if not market['contract']:
|
||
raise BadRequest(self.id + ' cancelOrders is only supported for swap markets.')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
# 'orderidlist': ids,
|
||
}
|
||
origClientOrderIdList = self.safe_list_2(params, 'origClientOrderIdList', 'clientOrderIds')
|
||
if origClientOrderIdList is not None:
|
||
params = self.omit(params, ['clientOrderIds'])
|
||
request['origClientOrderIdList'] = origClientOrderIdList
|
||
else:
|
||
request['orderidlist'] = ids
|
||
response = None
|
||
if market['linear']:
|
||
response = self.fapiPrivateDeleteBatchOrders(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPrivateDeleteBatchOrders(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "clientOrderId": "myOrder1",
|
||
# "cumQty": "0",
|
||
# "cumQuote": "0",
|
||
# "executedQty": "0",
|
||
# "orderId": 283194212,
|
||
# "origQty": "11",
|
||
# "origType": "TRAILING_STOP_MARKET",
|
||
# "price": "0",
|
||
# "reduceOnly": False,
|
||
# "side": "BUY",
|
||
# "positionSide": "SHORT",
|
||
# "status": "CANCELED",
|
||
# "stopPrice": "9300", # please ignore when order type is TRAILING_STOP_MARKET
|
||
# "closePosition": False, # if Close-All
|
||
# "symbol": "BTCUSDT",
|
||
# "timeInForce": "GTC",
|
||
# "type": "TRAILING_STOP_MARKET",
|
||
# "activatePrice": "9020", # activation price, only return with TRAILING_STOP_MARKET order
|
||
# "priceRate": "0.3", # callback rate, only return with TRAILING_STOP_MARKET order
|
||
# "updateTime": 1571110484038,
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False, # if conditional order trigger is protected
|
||
# "priceMatch": "NONE", # price match mode
|
||
# "selfTradePreventionMode": "NONE", # self trading preventation mode
|
||
# "goodTillDate": 0 # order pre-set auot cancel time for TIF GTD order
|
||
# },
|
||
# {
|
||
# "code": -2011,
|
||
# "msg": "Unknown order sent."
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_orders(response, market)
|
||
|
||
def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch all the trades made from a single order
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/account-endpoints#account-trade-list-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Account-Trade-List
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Account-Trade-List
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-Trade-List
|
||
|
||
:param str id: order id
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch trades for
|
||
:param int [limit]: the maximum number of trades to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchOrderTrades() requires a symbol argument')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
type = self.safe_string(params, 'type', market['type'])
|
||
params = self.omit(params, 'type')
|
||
if type != 'spot':
|
||
raise NotSupported(self.id + ' fetchOrderTrades() supports spot markets only')
|
||
request: dict = {
|
||
'orderId': id,
|
||
}
|
||
return self.fetch_my_trades(symbol, since, limit, self.extend(request, params))
|
||
|
||
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch all trades made by the user
|
||
|
||
https://developers.binance.com/docs/binance-spot-api-docs/rest-api/account-endpoints#account-trade-list-user_data
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Account-Trade-List
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Account-Trade-List
|
||
https://developers.binance.com/docs/margin_trading/trade/Query-Margin-Account-Trade-List
|
||
https://developers.binance.com/docs/derivatives/option/trade/Account-Trade-List
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Account-Trade-List
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Account-Trade-List
|
||
|
||
: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 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)
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch trades for a portfolio margin account
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params)
|
||
request: dict = {}
|
||
market = None
|
||
type = None
|
||
marginMode = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
type, params = self.handle_market_type_and_params('fetchMyTrades', market, params)
|
||
endTime = self.safe_integer_2(params, 'until', 'endTime')
|
||
if since is not None:
|
||
startTime = since
|
||
request['startTime'] = startTime
|
||
# If startTime and endTime are both not sent, then the last 7 days' data will be returned.
|
||
# The time between startTime and endTime cannot be longer than 7 days.
|
||
# The parameter fromId cannot be sent with startTime or endTime.
|
||
currentTimestamp = self.milliseconds()
|
||
oneWeek = 7 * 24 * 60 * 60 * 1000
|
||
if (currentTimestamp - startTime) >= oneWeek:
|
||
if (endTime is None) and market['linear']:
|
||
endTime = self.sum(startTime, oneWeek)
|
||
endTime = min(endTime, currentTimestamp)
|
||
if endTime is not None:
|
||
request['endTime'] = endTime
|
||
params = self.omit(params, ['endTime', 'until'])
|
||
if limit is not None:
|
||
if (type == 'option') or market['contract']:
|
||
limit = min(limit, 1000) # above 1000, returns error
|
||
request['limit'] = limit
|
||
response = None
|
||
if type == 'option':
|
||
response = self.eapiPrivateGetUserTrades(self.extend(request, params))
|
||
else:
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
|
||
marginMode, params = self.handle_margin_mode_and_params('fetchMyTrades', params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchMyTrades', 'papi', 'portfolioMargin', False)
|
||
if type == 'spot' or type == 'margin':
|
||
if isPortfolioMargin:
|
||
response = self.papiGetMarginMyTrades(self.extend(request, params))
|
||
elif (type == 'margin') or (marginMode is not None):
|
||
if marginMode == 'isolated':
|
||
request['isIsolated'] = True
|
||
response = self.sapiGetMarginMyTrades(self.extend(request, params))
|
||
else:
|
||
response = self.privateGetMyTrades(self.extend(request, params))
|
||
elif market['linear']:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmUserTrades(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetUserTrades(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmUserTrades(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetUserTrades(self.extend(request, params))
|
||
#
|
||
# spot trade
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BNBBTC",
|
||
# "id": 28457,
|
||
# "orderId": 100234,
|
||
# "price": "4.00000100",
|
||
# "qty": "12.00000000",
|
||
# "commission": "10.10000000",
|
||
# "commissionAsset": "BNB",
|
||
# "time": 1499865549590,
|
||
# "isBuyer": True,
|
||
# "isMaker": False,
|
||
# "isBestMatch": True,
|
||
# }
|
||
# ]
|
||
#
|
||
# futures trade
|
||
#
|
||
# [
|
||
# {
|
||
# "accountId": 20,
|
||
# "buyer": False,
|
||
# "commission": "-0.07819010",
|
||
# "commissionAsset": "USDT",
|
||
# "counterPartyId": 653,
|
||
# "id": 698759,
|
||
# "maker": False,
|
||
# "orderId": 25851813,
|
||
# "price": "7819.01",
|
||
# "qty": "0.002",
|
||
# "quoteQty": "0.01563",
|
||
# "realizedPnl": "-0.91539999",
|
||
# "side": "SELL",
|
||
# "symbol": "BTCUSDT",
|
||
# "time": 1569514978020
|
||
# }
|
||
# ]
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# [
|
||
# {
|
||
# "id": 1125899906844226012,
|
||
# "tradeId": 73,
|
||
# "orderId": 4638761100843040768,
|
||
# "symbol": "ETH-230211-1500-C",
|
||
# "price": "18.70000000",
|
||
# "quantity": "-0.57000000",
|
||
# "fee": "0.17305890",
|
||
# "realizedProfit": "-3.53400000",
|
||
# "side": "SELL",
|
||
# "type": "LIMIT",
|
||
# "volatility": "0.30000000",
|
||
# "liquidity": "MAKER",
|
||
# "time": 1676085216845,
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "quoteAsset": "USDT"
|
||
# }
|
||
# ]
|
||
#
|
||
# linear portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "id": 4575108247,
|
||
# "orderId": 261942655610,
|
||
# "side": "SELL",
|
||
# "price": "47263.40",
|
||
# "qty": "0.010",
|
||
# "realizedPnl": "27.38400000",
|
||
# "marginAsset": "USDT",
|
||
# "quoteQty": "472.63",
|
||
# "commission": "0.18905360",
|
||
# "commissionAsset": "USDT",
|
||
# "time": 1707530039409,
|
||
# "buyer": False,
|
||
# "maker": False,
|
||
# "positionSide": "LONG"
|
||
# }
|
||
# ]
|
||
#
|
||
# inverse portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "id": 701907838,
|
||
# "orderId": 71548909034,
|
||
# "pair": "ETHUSD",
|
||
# "side": "SELL",
|
||
# "price": "2498.15",
|
||
# "qty": "1",
|
||
# "realizedPnl": "0.00012517",
|
||
# "marginAsset": "ETH",
|
||
# "baseQty": "0.00400296",
|
||
# "commission": "0.00000160",
|
||
# "commissionAsset": "ETH",
|
||
# "time": 1707530317519,
|
||
# "positionSide": "LONG",
|
||
# "buyer": False,
|
||
# "maker": False
|
||
# }
|
||
# ]
|
||
#
|
||
# spot margin portfolio margin
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ADAUSDT",
|
||
# "id": 470227543,
|
||
# "orderId": 4421170947,
|
||
# "price": "0.53880000",
|
||
# "qty": "10.00000000",
|
||
# "quoteQty": "5.38800000",
|
||
# "commission": "0.00538800",
|
||
# "commissionAsset": "USDT",
|
||
# "time": 1707545780522,
|
||
# "isBuyer": False,
|
||
# "isMaker": False,
|
||
# "isBestMatch": True
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_trades(response, market, since, limit)
|
||
|
||
def fetch_my_dust_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch all dust trades made by the user
|
||
|
||
https://developers.binance.com/docs/wallet/asset/dust-log
|
||
|
||
:param str symbol: not used by binance fetchMyDustTrades()
|
||
:param int [since]: the earliest time in ms to fetch my dust trades for
|
||
:param int [limit]: the maximum number of dust trades to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: 'spot' or 'margin', default spot
|
||
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
||
"""
|
||
#
|
||
# Binance provides an opportunity to trade insignificant(i.e. non-tradable and non-withdrawable)
|
||
# token leftovers(of any asset) into `BNB` coin which in turn can be used to pay trading fees with it.
|
||
# The corresponding trades history is called the `Dust Log` and can be requested via the following end-point:
|
||
# https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md#dustlog-user_data
|
||
#
|
||
self.load_markets()
|
||
request: dict = {}
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
request['endTime'] = self.sum(since, 7776000000)
|
||
accountType = self.safe_string_upper(params, 'type')
|
||
params = self.omit(params, 'type')
|
||
if accountType is not None:
|
||
request['accountType'] = accountType
|
||
response = self.sapiGetAssetDribblet(self.extend(request, params))
|
||
# {
|
||
# "total": "4",
|
||
# "userAssetDribblets": [
|
||
# {
|
||
# "operateTime": "1627575731000",
|
||
# "totalServiceChargeAmount": "0.00001453",
|
||
# "totalTransferedAmount": "0.00072693",
|
||
# "transId": "70899815863",
|
||
# "userAssetDribbletDetails": [
|
||
# {
|
||
# "fromAsset": "LTC",
|
||
# "amount": "0.000006",
|
||
# "transferedAmount": "0.00000267",
|
||
# "serviceChargeAmount": "0.00000005",
|
||
# "operateTime": "1627575731000",
|
||
# "transId": "70899815863"
|
||
# },
|
||
# {
|
||
# "fromAsset": "GBP",
|
||
# "amount": "0.15949157",
|
||
# "transferedAmount": "0.00072426",
|
||
# "serviceChargeAmount": "0.00001448",
|
||
# "operateTime": "1627575731000",
|
||
# "transId": "70899815863"
|
||
# }
|
||
# ]
|
||
# },
|
||
# ]
|
||
# }
|
||
results = self.safe_list(response, 'userAssetDribblets', [])
|
||
rows = self.safe_integer(response, 'total', 0)
|
||
data = []
|
||
for i in range(0, rows):
|
||
logs = self.safe_list(results[i], 'userAssetDribbletDetails', [])
|
||
for j in range(0, len(logs)):
|
||
logs[j]['isDustTrade'] = True
|
||
data.append(logs[j])
|
||
trades = self.parse_trades(data, None, since, limit)
|
||
return self.filter_by_since_limit(trades, since, limit)
|
||
|
||
def parse_dust_trade(self, trade, market: Market = None):
|
||
#
|
||
# {
|
||
# "fromAsset": "USDT",
|
||
# "amount": "0.009669",
|
||
# "transferedAmount": "0.00002992",
|
||
# "serviceChargeAmount": "0.00000059",
|
||
# "operateTime": "1628076010000",
|
||
# "transId": "71416578712",
|
||
# "isDustTrade": True
|
||
# }
|
||
#
|
||
orderId = self.safe_string(trade, 'transId')
|
||
timestamp = self.safe_integer(trade, 'operateTime')
|
||
currencyId = self.safe_string(trade, 'fromAsset')
|
||
tradedCurrency = self.safe_currency_code(currencyId)
|
||
bnb = self.currency('BNB')
|
||
earnedCurrency = bnb['code']
|
||
applicantSymbol = earnedCurrency + '/' + tradedCurrency
|
||
tradedCurrencyIsQuote = False
|
||
if applicantSymbol in self.markets:
|
||
tradedCurrencyIsQuote = True
|
||
feeCostString = self.safe_string(trade, 'serviceChargeAmount')
|
||
fee = {
|
||
'currency': earnedCurrency,
|
||
'cost': self.parse_number(feeCostString),
|
||
}
|
||
symbol = None
|
||
amountString = None
|
||
costString = None
|
||
side = None
|
||
if tradedCurrencyIsQuote:
|
||
symbol = applicantSymbol
|
||
amountString = self.safe_string(trade, 'transferedAmount')
|
||
costString = self.safe_string(trade, 'amount')
|
||
side = 'buy'
|
||
else:
|
||
symbol = tradedCurrency + '/' + earnedCurrency
|
||
amountString = self.safe_string(trade, 'amount')
|
||
costString = self.safe_string(trade, 'transferedAmount')
|
||
side = 'sell'
|
||
priceString = None
|
||
if costString is not None:
|
||
if amountString:
|
||
priceString = Precise.string_div(costString, amountString)
|
||
id = None
|
||
amount = self.parse_number(amountString)
|
||
price = self.parse_number(priceString)
|
||
cost = self.parse_number(costString)
|
||
type = None
|
||
takerOrMaker = None
|
||
return {
|
||
'id': id,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': symbol,
|
||
'order': orderId,
|
||
'type': type,
|
||
'takerOrMaker': takerOrMaker,
|
||
'side': side,
|
||
'amount': amount,
|
||
'price': price,
|
||
'cost': cost,
|
||
'fee': fee,
|
||
'info': trade,
|
||
}
|
||
|
||
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
fetch all deposits made to an account
|
||
|
||
https://developers.binance.com/docs/wallet/capital/deposite-history
|
||
https://developers.binance.com/docs/fiat/rest-api/Get-Fiat-Deposit-Withdraw-History
|
||
|
||
: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
|
||
:param bool [params.fiat]: if True, only fiat deposits will be returned
|
||
:param int [params.until]: the latest time in ms to fetch entries for
|
||
: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 dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchDeposits', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_dynamic('fetchDeposits', code, since, limit, params)
|
||
currency = None
|
||
response = None
|
||
request: dict = {}
|
||
legalMoney = self.safe_dict(self.options, 'legalMoney', {})
|
||
fiatOnly = self.safe_bool(params, 'fiat', False)
|
||
params = self.omit(params, 'fiatOnly')
|
||
until = self.safe_integer(params, 'until')
|
||
params = self.omit(params, 'until')
|
||
if fiatOnly or (code in legalMoney):
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['transactionType'] = 0
|
||
if since is not None:
|
||
request['beginTime'] = since
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
raw = self.sapiGetFiatOrders(self.extend(request, params))
|
||
response = self.safe_list(raw, 'data', [])
|
||
# {
|
||
# "code": "000000",
|
||
# "message": "success",
|
||
# "data": [
|
||
# {
|
||
# "orderNo": "25ced37075c1470ba8939d0df2316e23",
|
||
# "fiatCurrency": "EUR",
|
||
# "indicatedAmount": "15.00",
|
||
# "amount": "15.00",
|
||
# "totalFee": "0.00",
|
||
# "method": "card",
|
||
# "status": "Failed",
|
||
# "createTime": 1627501026000,
|
||
# "updateTime": 1627501027000
|
||
# }
|
||
# ],
|
||
# "total": 1,
|
||
# "success": True
|
||
# }
|
||
else:
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
# max 3 months range https://github.com/ccxt/ccxt/issues/6495
|
||
endTime = self.sum(since, 7776000000)
|
||
if until is not None:
|
||
endTime = min(endTime, until)
|
||
request['endTime'] = endTime
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.sapiGetCapitalDepositHisrec(self.extend(request, params))
|
||
# [
|
||
# {
|
||
# "amount": "0.01844487",
|
||
# "coin": "BCH",
|
||
# "network": "BCH",
|
||
# "status": 1,
|
||
# "address": "1NYxAJhW2281HK1KtJeaENBqHeygA88FzR",
|
||
# "addressTag": "",
|
||
# "txId": "bafc5902504d6504a00b7d0306a41154cbf1d1b767ab70f3bc226327362588af",
|
||
# "insertTime": 1610784980000,
|
||
# "transferType": 0,
|
||
# "confirmTimes": "2/2"
|
||
# },
|
||
# {
|
||
# "amount": "4500",
|
||
# "coin": "USDT",
|
||
# "network": "BSC",
|
||
# "status": 1,
|
||
# "address": "0xc9c923c87347ca0f3451d6d308ce84f691b9f501",
|
||
# "addressTag": "",
|
||
# "txId": "Internal transfer 51376627901",
|
||
# "insertTime": 1618394381000,
|
||
# "transferType": 1,
|
||
# "confirmTimes": "1/15"
|
||
# }
|
||
# ]
|
||
for i in range(0, len(response)):
|
||
response[i]['type'] = 'deposit'
|
||
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://developers.binance.com/docs/wallet/capital/withdraw-history
|
||
https://developers.binance.com/docs/fiat/rest-api/Get-Fiat-Deposit-Withdraw-History
|
||
|
||
: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
|
||
:param bool [params.fiat]: if True, only fiat withdrawals will be returned
|
||
:param int [params.until]: the latest time in ms to fetch withdrawals for
|
||
: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 dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchWithdrawals', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_dynamic('fetchWithdrawals', code, since, limit, params)
|
||
legalMoney = self.safe_dict(self.options, 'legalMoney', {})
|
||
fiatOnly = self.safe_bool(params, 'fiat', False)
|
||
params = self.omit(params, 'fiatOnly')
|
||
request: dict = {}
|
||
until = self.safe_integer(params, 'until')
|
||
if until is not None:
|
||
params = self.omit(params, 'until')
|
||
request['endTime'] = until
|
||
response = None
|
||
currency = None
|
||
if fiatOnly or (code in legalMoney):
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['transactionType'] = 1
|
||
if since is not None:
|
||
request['beginTime'] = since
|
||
raw = self.sapiGetFiatOrders(self.extend(request, params))
|
||
response = self.safe_list(raw, 'data', [])
|
||
# {
|
||
# "code": "000000",
|
||
# "message": "success",
|
||
# "data": [
|
||
# {
|
||
# "orderNo": "CJW706452266115170304",
|
||
# "fiatCurrency": "GBP",
|
||
# "indicatedAmount": "10001.50",
|
||
# "amount": "100.00",
|
||
# "totalFee": "1.50",
|
||
# "method": "bank transfer",
|
||
# "status": "Successful",
|
||
# "createTime": 1620037745000,
|
||
# "updateTime": 1620038480000
|
||
# },
|
||
# {
|
||
# "orderNo": "CJW706287492781891584",
|
||
# "fiatCurrency": "GBP",
|
||
# "indicatedAmount": "10001.50",
|
||
# "amount": "100.00",
|
||
# "totalFee": "1.50",
|
||
# "method": "bank transfer",
|
||
# "status": "Successful",
|
||
# "createTime": 1619998460000,
|
||
# "updateTime": 1619998823000
|
||
# }
|
||
# ],
|
||
# "total": 39,
|
||
# "success": True
|
||
# }
|
||
else:
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['coin'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
# max 3 months range https://github.com/ccxt/ccxt/issues/6495
|
||
request['endTime'] = self.sum(since, 7776000000)
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.sapiGetCapitalWithdrawHistory(self.extend(request, params))
|
||
# [
|
||
# {
|
||
# "id": "69e53ad305124b96b43668ceab158a18",
|
||
# "amount": "28.75",
|
||
# "transactionFee": "0.25",
|
||
# "coin": "XRP",
|
||
# "status": 6,
|
||
# "address": "r3T75fuLjX51mmfb5Sk1kMNuhBgBPJsjza",
|
||
# "addressTag": "101286922",
|
||
# "txId": "19A5B24ED0B697E4F0E9CD09FCB007170A605BC93C9280B9E6379C5E6EF0F65A",
|
||
# "applyTime": "2021-04-15 12:09:16",
|
||
# "network": "XRP",
|
||
# "transferType": 0
|
||
# },
|
||
# {
|
||
# "id": "9a67628b16ba4988ae20d329333f16bc",
|
||
# "amount": "20",
|
||
# "transactionFee": "20",
|
||
# "coin": "USDT",
|
||
# "status": 6,
|
||
# "address": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1",
|
||
# "txId": "0x77fbf2cf2c85b552f0fd31fd2e56dc95c08adae031d96f3717d8b17e1aea3e46",
|
||
# "applyTime": "2021-04-15 12:06:53",
|
||
# "network": "ETH",
|
||
# "transferType": 0
|
||
# },
|
||
# {
|
||
# "id": "a7cdc0afbfa44a48bd225c9ece958fe2",
|
||
# "amount": "51",
|
||
# "transactionFee": "1",
|
||
# "coin": "USDT",
|
||
# "status": 6,
|
||
# "address": "TYDmtuWL8bsyjvcauUTerpfYyVhFtBjqyo",
|
||
# "txId": "168a75112bce6ceb4823c66726ad47620ad332e69fe92d9cb8ceb76023f9a028",
|
||
# "applyTime": "2021-04-13 12:46:59",
|
||
# "network": "TRX",
|
||
# "transferType": 0
|
||
# }
|
||
# ]
|
||
for i in range(0, len(response)):
|
||
response[i]['type'] = 'withdrawal'
|
||
return self.parse_transactions(response, currency, since, limit)
|
||
|
||
def parse_transaction_status_by_type(self, status, type=None):
|
||
if type is None:
|
||
return status
|
||
statusesByType: dict = {
|
||
'deposit': {
|
||
'0': 'pending',
|
||
'1': 'ok',
|
||
'6': 'ok',
|
||
# Fiat
|
||
# Processing, Failed, Successful, Finished, Refunding, Refunded, Refund Failed, Order Partial credit Stopped
|
||
'Processing': 'pending',
|
||
'Failed': 'failed',
|
||
'Successful': 'ok',
|
||
'Refunding': 'canceled',
|
||
'Refunded': 'canceled',
|
||
'Refund Failed': 'failed',
|
||
},
|
||
'withdrawal': {
|
||
'0': 'pending', # Email Sent
|
||
'1': 'canceled', # Cancelled(different from 1 = ok in deposits)
|
||
'2': 'pending', # Awaiting Approval
|
||
'3': 'failed', # Rejected
|
||
'4': 'pending', # Processing
|
||
'5': 'failed', # Failure
|
||
'6': 'ok', # Completed
|
||
# Fiat
|
||
# Processing, Failed, Successful, Finished, Refunding, Refunded, Refund Failed, Order Partial credit Stopped
|
||
'Processing': 'pending',
|
||
'Failed': 'failed',
|
||
'Successful': 'ok',
|
||
'Refunding': 'canceled',
|
||
'Refunded': 'canceled',
|
||
'Refund Failed': 'failed',
|
||
},
|
||
}
|
||
statuses = self.safe_dict(statusesByType, type, {})
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
||
#
|
||
# fetchDeposits
|
||
#
|
||
# {
|
||
# "amount": "4500",
|
||
# "coin": "USDT",
|
||
# "network": "BSC",
|
||
# "status": 1,
|
||
# "address": "0xc9c923c87347ca0f3451d6d308ce84f691b9f501",
|
||
# "addressTag": "",
|
||
# "txId": "Internal transfer 51376627901",
|
||
# "insertTime": 1618394381000,
|
||
# "transferType": 1,
|
||
# "confirmTimes": "1/15"
|
||
# }
|
||
#
|
||
# fetchWithdrawals
|
||
#
|
||
# {
|
||
# "id": "69e53ad305124b96b43668ceab158a18",
|
||
# "amount": "28.75",
|
||
# "transactionFee": "0.25",
|
||
# "coin": "XRP",
|
||
# "status": 6,
|
||
# "address": "r3T75fuLjX51mmfb5Sk1kMNuhBgBPJsjza",
|
||
# "addressTag": "101286922",
|
||
# "txId": "19A5B24ED0B697E4F0E9CD09FCB007170A605BC93C9280B9E6379C5E6EF0F65A",
|
||
# "applyTime": "2021-04-15 12:09:16",
|
||
# "network": "XRP",
|
||
# "transferType": 0
|
||
# }
|
||
#
|
||
# fiat transaction
|
||
# withdraw
|
||
# {
|
||
# "orderNo": "CJW684897551397171200",
|
||
# "fiatCurrency": "GBP",
|
||
# "indicatedAmount": "29.99",
|
||
# "amount": "28.49",
|
||
# "totalFee": "1.50",
|
||
# "method": "bank transfer",
|
||
# "status": "Successful",
|
||
# "createTime": 1614898701000,
|
||
# "updateTime": 1614898820000
|
||
# }
|
||
#
|
||
# deposit
|
||
# {
|
||
# "orderNo": "25ced37075c1470ba8939d0df2316e23",
|
||
# "fiatCurrency": "EUR",
|
||
# "transactionType": 0,
|
||
# "indicatedAmount": "15.00",
|
||
# "amount": "15.00",
|
||
# "totalFee": "0.00",
|
||
# "method": "card",
|
||
# "status": "Failed",
|
||
# "createTime": "1627501026000",
|
||
# "updateTime": "1627501027000"
|
||
# }
|
||
#
|
||
# withdraw
|
||
#
|
||
# {id: "9a67628b16ba4988ae20d329333f16bc"}
|
||
#
|
||
id = self.safe_string_2(transaction, 'id', 'orderNo')
|
||
address = self.safe_string(transaction, 'address')
|
||
tag = self.safe_string(transaction, 'addressTag') # set but unused
|
||
if tag is not None:
|
||
if len(tag) < 1:
|
||
tag = None
|
||
txid = self.safe_string(transaction, 'txId')
|
||
if (txid is not None) and (txid.find('Internal transfer ') >= 0):
|
||
txid = txid[18:]
|
||
currencyId = self.safe_string_2(transaction, 'coin', 'fiatCurrency')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
timestamp = None
|
||
timestamp = self.safe_integer_2(transaction, 'insertTime', 'createTime')
|
||
if timestamp is None:
|
||
timestamp = self.parse8601(self.safe_string(transaction, 'applyTime'))
|
||
updated = self.safe_integer_2(transaction, 'successTime', 'updateTime')
|
||
type = self.safe_string(transaction, 'type')
|
||
if type is None:
|
||
txType = self.safe_string(transaction, 'transactionType')
|
||
if txType is not None:
|
||
type = 'deposit' if (txType == '0') else 'withdrawal'
|
||
legalMoneyCurrenciesById = self.safe_dict(self.options, 'legalMoneyCurrenciesById')
|
||
code = self.safe_string(legalMoneyCurrenciesById, code, code)
|
||
status = self.parse_transaction_status_by_type(self.safe_string(transaction, 'status'), type)
|
||
amount = self.safe_number(transaction, 'amount')
|
||
feeCost = self.safe_number_2(transaction, 'transactionFee', 'totalFee')
|
||
fee = None
|
||
if feeCost is not None:
|
||
fee = {'currency': code, 'cost': feeCost}
|
||
internalInteger = self.safe_integer(transaction, 'transferType')
|
||
internal = None
|
||
if internalInteger is not None:
|
||
internal = True if (internalInteger != 0) else False
|
||
network = self.safe_string(transaction, 'network')
|
||
return {
|
||
'info': transaction,
|
||
'id': id,
|
||
'txid': txid,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'network': network,
|
||
'address': address,
|
||
'addressTo': address,
|
||
'addressFrom': None,
|
||
'tag': tag,
|
||
'tagTo': tag,
|
||
'tagFrom': None,
|
||
'type': type,
|
||
'amount': amount,
|
||
'currency': code,
|
||
'status': status,
|
||
'updated': updated,
|
||
'internal': internal,
|
||
'comment': None,
|
||
'fee': fee,
|
||
}
|
||
|
||
def parse_transfer_status(self, status: Str) -> Str:
|
||
statuses: dict = {
|
||
'CONFIRMED': 'ok',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
||
#
|
||
# transfer
|
||
#
|
||
# {
|
||
# "tranId":13526853623
|
||
# }
|
||
#
|
||
# fetchTransfers
|
||
#
|
||
# {
|
||
# "timestamp": 1614640878000,
|
||
# "asset": "USDT",
|
||
# "amount": "25",
|
||
# "type": "MAIN_UMFUTURE",
|
||
# "status": "CONFIRMED",
|
||
# "tranId": 43000126248
|
||
# }
|
||
#
|
||
# {
|
||
# "orderType": "C2C", # Enum:PAY(C2B Merchant Acquiring Payment), PAY_REFUND(C2B Merchant Acquiring Payment,refund), C2C(C2C Transfer Payment),CRYPTO_BOX(Crypto box), CRYPTO_BOX_RF(Crypto Box, refund), C2C_HOLDING(Transfer to new Binance user), C2C_HOLDING_RF(Transfer to new Binance user,refund), PAYOUT(B2C Disbursement Payment), REMITTANCE(Send cash)
|
||
# "transactionId": "M_P_71505104267788288",
|
||
# "transactionTime": 1610090460133, #trade timestamp
|
||
# "amount": "23.72469206", #order amount(up to 8 decimal places), positive is income, negative is expenditure
|
||
# "currency": "BNB",
|
||
# "walletType": 1, #main wallet type, 1 for funding wallet, 2 for spot wallet, 3 for fiat wallet, 4 or 6 for card payment, 5 for earn wallet
|
||
# "walletTypes": [1,2], #array format,there are multiple values when using combination payment
|
||
# "fundsDetail": [ # details
|
||
# {
|
||
# "currency": "USDT", #asset
|
||
# "amount": "1.2",
|
||
# "walletAssetCost":[ #details of asset cost per wallet
|
||
# {"1":"0.6"},
|
||
# {"2":"0.6"}
|
||
# ]
|
||
# },
|
||
# {
|
||
# "currency": "ETH",
|
||
# "amount": "0.0001",
|
||
# "walletAssetCost":[
|
||
# {"1":"0.00005"},
|
||
# {"2":"0.00005"}
|
||
# ]
|
||
# }
|
||
# ],
|
||
# "payerInfo":{
|
||
# "name":"Jack", #nickname or merchant name
|
||
# "type":"USER", #account type,USER for personal,MERCHANT for merchant
|
||
# "binanceId":"12345678", #binance uid
|
||
# "accountId":"67736251" #binance pay id
|
||
# },
|
||
# "receiverInfo":{
|
||
# "name":"Alan", #nickname or merchant name
|
||
# "type":"MERCHANT", #account type,USER for personal,MERCHANT for merchant
|
||
# "email":"alan@binance.com", #email
|
||
# "binanceId":"34355667", #binance uid
|
||
# "accountId":"21326891", #binance pay id
|
||
# "countryCode":"1", #International area code
|
||
# "phoneNumber":"8057651210",
|
||
# "mobileCode":"US", #country code
|
||
# "extend":[ #extension field
|
||
# "institutionName": "",
|
||
# "cardNumber": "",
|
||
# "digitalWalletId": ""
|
||
# ]
|
||
# }
|
||
# }
|
||
id = self.safe_string_2(transfer, 'tranId', 'transactionId')
|
||
currencyId = self.safe_string_2(transfer, 'asset', 'currency')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
amount = self.safe_number(transfer, 'amount')
|
||
type = self.safe_string(transfer, 'type')
|
||
fromAccount = None
|
||
toAccount = None
|
||
accountsById = self.safe_dict(self.options, 'accountsById', {})
|
||
if type is not None:
|
||
parts = type.split('_')
|
||
fromAccount = self.safe_value(parts, 0)
|
||
toAccount = self.safe_value(parts, 1)
|
||
fromAccount = self.safe_string(accountsById, fromAccount, fromAccount)
|
||
toAccount = self.safe_string(accountsById, toAccount, toAccount)
|
||
walletType = self.safe_integer(transfer, 'walletType')
|
||
if walletType is not None:
|
||
payer = self.safe_dict(transfer, 'payerInfo', {})
|
||
receiver = self.safe_dict(transfer, 'receiverInfo', {})
|
||
fromAccount = self.safe_string(payer, 'accountId')
|
||
toAccount = self.safe_string(receiver, 'accountId')
|
||
timestamp = self.safe_integer_2(transfer, 'timestamp', 'transactionTime')
|
||
status = self.parse_transfer_status(self.safe_string(transfer, 'status'))
|
||
return {
|
||
'info': transfer,
|
||
'id': id,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'currency': code,
|
||
'amount': amount,
|
||
'fromAccount': fromAccount,
|
||
'toAccount': toAccount,
|
||
'status': status,
|
||
}
|
||
|
||
def parse_income(self, income, market: Market = None):
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSDT",
|
||
# "incomeType": "FUNDING_FEE",
|
||
# "income": "0.00134317",
|
||
# "asset": "USDT",
|
||
# "time": "1621584000000",
|
||
# "info": "FUNDING_FEE",
|
||
# "tranId": "4480321991774044580",
|
||
# "tradeId": ""
|
||
# }
|
||
#
|
||
marketId = self.safe_string(income, 'symbol')
|
||
currencyId = self.safe_string(income, 'asset')
|
||
timestamp = self.safe_integer(income, 'time')
|
||
return {
|
||
'info': income,
|
||
'symbol': self.safe_symbol(marketId, market, None, 'swap'),
|
||
'code': self.safe_currency_code(currencyId),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'id': self.safe_string(income, 'tranId'),
|
||
'amount': self.safe_number(income, 'income'),
|
||
}
|
||
|
||
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
||
"""
|
||
transfer currency internally between wallets on the same account
|
||
|
||
https://developers.binance.com/docs/wallet/asset/user-universal-transfer
|
||
|
||
:param str code: unified currency code
|
||
:param float amount: amount to transfer
|
||
:param str fromAccount: account to transfer from
|
||
:param str toAccount: account to transfer to
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.type]: exchange specific transfer type
|
||
:param str [params.symbol]: the unified symbol, required for isolated margin transfers
|
||
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
'amount': self.currency_to_precision(code, amount),
|
||
}
|
||
request['type'] = self.safe_string(params, 'type')
|
||
params = self.omit(params, 'type')
|
||
if request['type'] is None:
|
||
symbol = self.safe_string(params, 'symbol')
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
params = self.omit(params, 'symbol')
|
||
fromId = self.convert_type_to_account(fromAccount).upper()
|
||
toId = self.convert_type_to_account(toAccount).upper()
|
||
isolatedSymbol = None
|
||
if market is not None:
|
||
isolatedSymbol = market['id']
|
||
if fromId == 'ISOLATED':
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' transfer() requires params["symbol"] when fromAccount is ' + fromAccount)
|
||
if toId == 'ISOLATED':
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' transfer() requires params["symbol"] when toAccount is ' + toAccount)
|
||
accountsById = self.safe_dict(self.options, 'accountsById', {})
|
||
fromIsolated = not (fromId in accountsById)
|
||
toIsolated = not (toId in accountsById)
|
||
if fromIsolated and (market is None):
|
||
isolatedSymbol = fromId # allow user provide symbol from/to account
|
||
if toIsolated and (market is None):
|
||
isolatedSymbol = toId
|
||
if fromIsolated or toIsolated: # Isolated margin transfer
|
||
fromFuture = fromId == 'UMFUTURE' or fromId == 'CMFUTURE'
|
||
toFuture = toId == 'UMFUTURE' or toId == 'CMFUTURE'
|
||
fromSpot = fromId == 'MAIN'
|
||
toSpot = toId == 'MAIN'
|
||
funding = fromId == 'FUNDING' or toId == 'FUNDING'
|
||
option = fromId == 'OPTION' or toId == 'OPTION'
|
||
prohibitedWithIsolated = fromFuture or toFuture or funding or option
|
||
if (fromIsolated or toIsolated) and prohibitedWithIsolated:
|
||
raise BadRequest(self.id + ' transfer() does not allow transfers between ' + fromAccount + ' and ' + toAccount)
|
||
elif toSpot and fromIsolated:
|
||
fromId = 'ISOLATED_MARGIN'
|
||
request['fromSymbol'] = isolatedSymbol
|
||
elif fromSpot and toIsolated:
|
||
toId = 'ISOLATED_MARGIN'
|
||
request['toSymbol'] = isolatedSymbol
|
||
else:
|
||
if fromIsolated and toIsolated:
|
||
request['fromSymbol'] = fromId
|
||
request['toSymbol'] = toId
|
||
fromId = 'ISOLATEDMARGIN'
|
||
toId = 'ISOLATEDMARGIN'
|
||
else:
|
||
if fromIsolated:
|
||
request['fromSymbol'] = isolatedSymbol
|
||
fromId = 'ISOLATEDMARGIN'
|
||
if toIsolated:
|
||
request['toSymbol'] = isolatedSymbol
|
||
toId = 'ISOLATEDMARGIN'
|
||
request['type'] = fromId + '_' + toId
|
||
else:
|
||
request['type'] = fromId + '_' + toId
|
||
response = self.sapiPostAssetTransfer(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId":13526853623
|
||
# }
|
||
#
|
||
return self.parse_transfer(response, currency)
|
||
|
||
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://developers.binance.com/docs/wallet/asset/query-user-universal-transfer
|
||
|
||
:param str code: unified currency code of the currency transferred
|
||
:param int [since]: the earliest time in ms to fetch transfers for
|
||
:param int [limit]: the maximum number of transfers structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: the latest time in ms to fetch transfers for
|
||
:param 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)
|
||
:param boolean [params.internal]: default False, when True will fetch pay trade history
|
||
:returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
|
||
"""
|
||
self.load_markets()
|
||
internal = self.safe_bool(params, 'internal')
|
||
params = self.omit(params, 'internal')
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchTransfers', 'paginate')
|
||
if paginate and not internal:
|
||
return self.fetch_paginated_call_dynamic('fetchTransfers', code, since, limit, params)
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request: dict = {}
|
||
limitKey = 'limit'
|
||
if not internal:
|
||
defaultType = self.safe_string_2(self.options, 'fetchTransfers', 'defaultType', 'spot')
|
||
fromAccount = self.safe_string(params, 'fromAccount', defaultType)
|
||
defaultTo = 'spot' if (fromAccount == 'future') else 'future'
|
||
toAccount = self.safe_string(params, 'toAccount', defaultTo)
|
||
type = self.safe_string(params, 'type')
|
||
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
|
||
fromId = self.safe_string(accountsByType, fromAccount)
|
||
toId = self.safe_string(accountsByType, toAccount)
|
||
if type is None:
|
||
if fromId is None:
|
||
keys = list(accountsByType.keys())
|
||
raise ExchangeError(self.id + ' fromAccount parameter must be one of ' + ', '.join(keys))
|
||
if toId is None:
|
||
keys = list(accountsByType.keys())
|
||
raise ExchangeError(self.id + ' toAccount parameter must be one of ' + ', '.join(keys))
|
||
type = fromId + '_' + toId
|
||
request['type'] = type
|
||
limitKey = 'size'
|
||
if limit is not None:
|
||
request[limitKey] = limit
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until = self.safe_integer(params, 'until')
|
||
if until is not None:
|
||
params = self.omit(params, 'until')
|
||
request['endTime'] = until
|
||
response = None
|
||
if internal:
|
||
response = self.sapiGetPayTransactions(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "000000",
|
||
# "message": "success",
|
||
# "data": [
|
||
# {
|
||
# "orderType": "C2C", # Enum:PAY(C2B Merchant Acquiring Payment), PAY_REFUND(C2B Merchant Acquiring Payment,refund), C2C(C2C Transfer Payment),CRYPTO_BOX(Crypto box), CRYPTO_BOX_RF(Crypto Box, refund), C2C_HOLDING(Transfer to new Binance user), C2C_HOLDING_RF(Transfer to new Binance user,refund), PAYOUT(B2C Disbursement Payment), REMITTANCE(Send cash)
|
||
# "transactionId": "M_P_71505104267788288",
|
||
# "transactionTime": 1610090460133, #trade timestamp
|
||
# "amount": "23.72469206", #order amount(up to 8 decimal places), positive is income, negative is expenditure
|
||
# "currency": "BNB",
|
||
# "walletType": 1, #main wallet type, 1 for funding wallet, 2 for spot wallet, 3 for fiat wallet, 4 or 6 for card payment, 5 for earn wallet
|
||
# "walletTypes": [1,2], #array format,there are multiple values when using combination payment
|
||
# "fundsDetail": [ # details
|
||
# {
|
||
# "currency": "USDT", #asset
|
||
# "amount": "1.2",
|
||
# "walletAssetCost":[ #details of asset cost per wallet
|
||
# {"1":"0.6"},
|
||
# {"2":"0.6"}
|
||
# ]
|
||
# },
|
||
# {
|
||
# "currency": "ETH",
|
||
# "amount": "0.0001",
|
||
# "walletAssetCost":[
|
||
# {"1":"0.00005"},
|
||
# {"2":"0.00005"}
|
||
# ]
|
||
# }
|
||
# ],
|
||
# "payerInfo":{
|
||
# "name":"Jack", #nickname or merchant name
|
||
# "type":"USER", #account type,USER for personal,MERCHANT for merchant
|
||
# "binanceId":"12345678", #binance uid
|
||
# "accountId":"67736251" #binance pay id
|
||
# },
|
||
# "receiverInfo":{
|
||
# "name":"Alan", #nickname or merchant name
|
||
# "type":"MERCHANT", #account type,USER for personal,MERCHANT for merchant
|
||
# "email":"alan@binance.com", #email
|
||
# "binanceId":"34355667", #binance uid
|
||
# "accountId":"21326891", #binance pay id
|
||
# "countryCode":"1", #International area code
|
||
# "phoneNumber":"8057651210",
|
||
# "mobileCode":"US", #country code
|
||
# "extend":[ #extension field
|
||
# "institutionName": "",
|
||
# "cardNumber": "",
|
||
# "digitalWalletId": ""
|
||
# ]
|
||
# }
|
||
# }
|
||
# ],
|
||
# "success": True
|
||
# }
|
||
#
|
||
else:
|
||
response = self.sapiGetAssetTransfer(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "total": 3,
|
||
# "rows": [
|
||
# {
|
||
# "timestamp": 1614640878000,
|
||
# "asset": "USDT",
|
||
# "amount": "25",
|
||
# "type": "MAIN_UMFUTURE",
|
||
# "status": "CONFIRMED",
|
||
# "tranId": 43000126248
|
||
# },
|
||
# ]
|
||
# }
|
||
#
|
||
rows = self.safe_list_2(response, 'rows', 'data', [])
|
||
return self.parse_transfers(rows, currency, since, limit)
|
||
|
||
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
fetch the deposit address for a currency associated with self account
|
||
|
||
https://developers.binance.com/docs/wallet/capital/deposite-address
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.network]: network for fetch deposit address
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
# 'network': 'ETH', # 'BSC', 'XMR', you can get network and isDefault in networkList in the response of sapiGetCapitalConfigDetail
|
||
}
|
||
networks = self.safe_dict(self.options, 'networks', {})
|
||
network = self.safe_string_upper(params, 'network') # self line allows the user to specify either ERC20 or ETH
|
||
network = self.safe_string(networks, network, network) # handle ERC20>ETH alias
|
||
if network is not None:
|
||
request['network'] = network
|
||
params = self.omit(params, 'network')
|
||
# has support for the 'network' parameter
|
||
response = self.sapiGetCapitalDepositAddress(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "currency": "XRP",
|
||
# "address": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh",
|
||
# "tag": "108618262",
|
||
# "info": {
|
||
# "coin": "XRP",
|
||
# "address": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh",
|
||
# "tag": "108618262",
|
||
# "url": "https://bithomp.com/explorer/rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh"
|
||
# }
|
||
# }
|
||
#
|
||
return self.parse_deposit_address(response, currency)
|
||
|
||
def parse_deposit_address(self, response, currency: Currency = None) -> DepositAddress:
|
||
#
|
||
# {
|
||
# "coin": "XRP",
|
||
# "address": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh",
|
||
# "tag": "108618262",
|
||
# "url": "https://bithomp.com/explorer/rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh"
|
||
# }
|
||
#
|
||
url = self.safe_string(response, 'url')
|
||
address = self.safe_string(response, 'address')
|
||
currencyId = self.safe_string(response, 'currency')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
# deposit-address endpoint provides only network url(not network ID/CODE)
|
||
# so we should map the url to network(their data is inside currencies)
|
||
networkCode = self.get_network_code_by_network_url(code, url)
|
||
tag = self.safe_string(response, 'tag', '')
|
||
if len(tag) == 0:
|
||
tag = None
|
||
self.check_address(address)
|
||
return {
|
||
'info': response,
|
||
'currency': code,
|
||
'network': networkCode,
|
||
'address': address,
|
||
'tag': tag,
|
||
}
|
||
|
||
def fetch_transaction_fees(self, codes: Strings = None, params={}):
|
||
"""
|
||
@deprecated
|
||
please use fetchDepositWithdrawFees instead
|
||
|
||
https://developers.binance.com/docs/wallet/capital/all-coins-info
|
||
|
||
:param str[]|None codes: not used by binance fetchTransactionFees()
|
||
: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.sapiGetCapitalConfigGetall(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "coin": "BAT",
|
||
# "depositAllEnable": True,
|
||
# "withdrawAllEnable": True,
|
||
# "name": "Basic Attention Token",
|
||
# "free": "0",
|
||
# "locked": "0",
|
||
# "freeze": "0",
|
||
# "withdrawing": "0",
|
||
# "ipoing": "0",
|
||
# "ipoable": "0",
|
||
# "storage": "0",
|
||
# "isLegalMoney": False,
|
||
# "trading": True,
|
||
# "networkList": [
|
||
# {
|
||
# "network": "BNB",
|
||
# "coin": "BAT",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": False,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": '',
|
||
# "withdrawDesc": '',
|
||
# "specialTips": "The name of self asset is Basic Attention Token(BAT). Both a MEMO and an Address are required to successfully deposit your BEP2 tokens to Binance.",
|
||
# "name": "BEP2",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(bnb1)[0-9a-z]{38}$",
|
||
# "memoRegex": "^[0-9A-Za-z\\-_]{1,120}$",
|
||
# "withdrawFee": "0.27",
|
||
# "withdrawMin": "0.54",
|
||
# "withdrawMax": "10000000000",
|
||
# "minConfirm": "1",
|
||
# "unLockConfirm": "0"
|
||
# },
|
||
# {
|
||
# "network": "BSC",
|
||
# "coin": "BAT",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": False,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": '',
|
||
# "withdrawDesc": '',
|
||
# "specialTips": "The name of self asset is Basic Attention Token. Please ensure you are depositing Basic Attention Token(BAT) tokens under the contract address ending in 9766e.",
|
||
# "name": "BEP20(BSC)",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(0x)[0-9A-Fa-f]{40}$",
|
||
# "memoRegex": '',
|
||
# "withdrawFee": "0.27",
|
||
# "withdrawMin": "0.54",
|
||
# "withdrawMax": "10000000000",
|
||
# "minConfirm": "15",
|
||
# "unLockConfirm": "0"
|
||
# },
|
||
# {
|
||
# "network": "ETH",
|
||
# "coin": "BAT",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": True,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": '',
|
||
# "withdrawDesc": '',
|
||
# "specialTips": "The name of self asset is Basic Attention Token. Please ensure you are depositing Basic Attention Token(BAT) tokens under the contract address ending in 887ef.",
|
||
# "name": "ERC20",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(0x)[0-9A-Fa-f]{40}$",
|
||
# "memoRegex": '',
|
||
# "withdrawFee": "27",
|
||
# "withdrawMin": "54",
|
||
# "withdrawMax": "10000000000",
|
||
# "minConfirm": "12",
|
||
# "unLockConfirm": "0"
|
||
# }
|
||
# ]
|
||
# }
|
||
# ]
|
||
#
|
||
withdrawFees: dict = {}
|
||
for i in range(0, len(response)):
|
||
entry = response[i]
|
||
currencyId = self.safe_string(entry, 'coin')
|
||
code = self.safe_currency_code(currencyId)
|
||
networkList = self.safe_list(entry, 'networkList', [])
|
||
withdrawFees[code] = {}
|
||
for j in range(0, len(networkList)):
|
||
networkEntry = networkList[j]
|
||
networkId = self.safe_string(networkEntry, 'network')
|
||
networkCode = self.safe_currency_code(networkId)
|
||
fee = self.safe_number(networkEntry, 'withdrawFee')
|
||
withdrawFees[code][networkCode] = fee
|
||
return {
|
||
'withdraw': withdrawFees,
|
||
'deposit': {},
|
||
'info': response,
|
||
}
|
||
|
||
def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
||
"""
|
||
fetch deposit and withdraw fees
|
||
|
||
https://developers.binance.com/docs/wallet/capital/all-coins-info
|
||
|
||
:param str[]|None codes: not used by binance fetchDepositWithdrawFees()
|
||
: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.sapiGetCapitalConfigGetall(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "coin": "BAT",
|
||
# "depositAllEnable": True,
|
||
# "withdrawAllEnable": True,
|
||
# "name": "Basic Attention Token",
|
||
# "free": "0",
|
||
# "locked": "0",
|
||
# "freeze": "0",
|
||
# "withdrawing": "0",
|
||
# "ipoing": "0",
|
||
# "ipoable": "0",
|
||
# "storage": "0",
|
||
# "isLegalMoney": False,
|
||
# "trading": True,
|
||
# "networkList": [
|
||
# {
|
||
# "network": "BNB",
|
||
# "coin": "BAT",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": False,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": '',
|
||
# "withdrawDesc": '',
|
||
# "specialTips": "The name of self asset is Basic Attention Token(BAT). Both a MEMO and an Address are required to successfully deposit your BEP2 tokens to Binance.",
|
||
# "name": "BEP2",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(bnb1)[0-9a-z]{38}$",
|
||
# "memoRegex": "^[0-9A-Za-z\\-_]{1,120}$",
|
||
# "withdrawFee": "0.27",
|
||
# "withdrawMin": "0.54",
|
||
# "withdrawMax": "10000000000",
|
||
# "minConfirm": "1",
|
||
# "unLockConfirm": "0"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_deposit_withdraw_fees(response, codes, 'coin')
|
||
|
||
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "coin": "BAT",
|
||
# "depositAllEnable": True,
|
||
# "withdrawAllEnable": True,
|
||
# "name": "Basic Attention Token",
|
||
# "free": "0",
|
||
# "locked": "0",
|
||
# "freeze": "0",
|
||
# "withdrawing": "0",
|
||
# "ipoing": "0",
|
||
# "ipoable": "0",
|
||
# "storage": "0",
|
||
# "isLegalMoney": False,
|
||
# "trading": True,
|
||
# "networkList": [
|
||
# {
|
||
# "network": "BNB",
|
||
# "coin": "BAT",
|
||
# "withdrawIntegerMultiple": "0.00000001",
|
||
# "isDefault": False,
|
||
# "depositEnable": True,
|
||
# "withdrawEnable": True,
|
||
# "depositDesc": '',
|
||
# "withdrawDesc": '',
|
||
# "specialTips": "The name of self asset is Basic Attention Token(BAT). Both a MEMO and an Address are required to successfully deposit your BEP2 tokens to Binance.",
|
||
# "name": "BEP2",
|
||
# "resetAddressStatus": False,
|
||
# "addressRegex": "^(bnb1)[0-9a-z]{38}$",
|
||
# "memoRegex": "^[0-9A-Za-z\\-_]{1,120}$",
|
||
# "withdrawFee": "0.27",
|
||
# "withdrawMin": "0.54",
|
||
# "withdrawMax": "10000000000",
|
||
# "minConfirm": "1",
|
||
# "unLockConfirm": "0"
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
networkList = self.safe_list(fee, 'networkList', [])
|
||
result = self.deposit_withdraw_fee(fee)
|
||
for j in range(0, len(networkList)):
|
||
networkEntry = networkList[j]
|
||
networkId = self.safe_string(networkEntry, 'network')
|
||
networkCode = self.network_id_to_code(networkId)
|
||
withdrawFee = self.safe_number(networkEntry, 'withdrawFee')
|
||
isDefault = self.safe_bool(networkEntry, 'isDefault')
|
||
if isDefault is True:
|
||
result['withdraw'] = {
|
||
'fee': withdrawFee,
|
||
'percentage': None,
|
||
}
|
||
result['networks'][networkCode] = {
|
||
'withdraw': {
|
||
'fee': withdrawFee,
|
||
'percentage': None,
|
||
},
|
||
'deposit': {
|
||
'fee': None,
|
||
'percentage': None,
|
||
},
|
||
}
|
||
return result
|
||
|
||
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
||
"""
|
||
make a withdrawal
|
||
|
||
https://developers.binance.com/docs/wallet/capital/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
|
||
: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)
|
||
request: dict = {
|
||
'coin': currency['id'],
|
||
'address': address,
|
||
# issue sapiGetCapitalConfigGetall() to get networks for withdrawing USDT ERC20 vs USDT Omni
|
||
# 'network': 'ETH', # 'BTC', 'TRX', etc, optional
|
||
}
|
||
if tag is not None:
|
||
request['addressTag'] = tag
|
||
networks = self.safe_dict(self.options, 'networks', {})
|
||
network = self.safe_string_upper(params, 'network') # self line allows the user to specify either ERC20 or ETH
|
||
network = self.safe_string(networks, network, network) # handle ERC20>ETH alias
|
||
if network is not None:
|
||
request['network'] = network
|
||
params = self.omit(params, 'network')
|
||
request['amount'] = self.currency_to_precision(code, amount, network)
|
||
response = self.sapiPostCapitalWithdrawApply(self.extend(request, params))
|
||
# {id: '9a67628b16ba4988ae20d329333f16bc'}
|
||
return self.parse_transaction(response, currency)
|
||
|
||
def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
|
||
#
|
||
# spot
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "makerCommission": "0.001",
|
||
# "takerCommission": "0.001"
|
||
# }
|
||
# ]
|
||
#
|
||
# swap
|
||
# {
|
||
# "symbol": "BTCUSD_PERP",
|
||
# "makerCommissionRate": "0.00015", # 0.015%
|
||
# "takerCommissionRate": "0.00040" # 0.040%
|
||
# }
|
||
#
|
||
marketId = self.safe_string(fee, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market, None, 'spot')
|
||
return {
|
||
'info': fee,
|
||
'symbol': symbol,
|
||
'maker': self.safe_number_2(fee, 'makerCommission', 'makerCommissionRate'),
|
||
'taker': self.safe_number_2(fee, 'takerCommission', 'takerCommissionRate'),
|
||
'percentage': None,
|
||
'tierBased': None,
|
||
}
|
||
|
||
def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
||
"""
|
||
fetch the trading fees for a market
|
||
|
||
https://developers.binance.com/docs/wallet/asset/trade-fee
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/User-Commission-Rate
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/User-Commission-Rate
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-User-Commission-Rate-for-UM
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-User-Commission-Rate-for-CM
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch trading fees in a portfolio margin account
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
type = market['type']
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchTradingFee', market, params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchTradingFee', 'papi', 'portfolioMargin', False)
|
||
isLinear = self.is_linear(type, subType)
|
||
isInverse = self.is_inverse(type, subType)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = None
|
||
if isLinear:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmCommissionRate(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetCommissionRate(self.extend(request, params))
|
||
elif isInverse:
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmCommissionRate(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetCommissionRate(self.extend(request, params))
|
||
else:
|
||
response = self.sapiGetAssetTradeFee(self.extend(request, params))
|
||
#
|
||
# spot
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "makerCommission": "0.001",
|
||
# "takerCommission": "0.001"
|
||
# }
|
||
# ]
|
||
#
|
||
# swap
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSD_PERP",
|
||
# "makerCommissionRate": "0.00015", # 0.015%
|
||
# "takerCommissionRate": "0.00040" # 0.040%
|
||
# }
|
||
#
|
||
data = response
|
||
if isinstance(data, list):
|
||
data = self.safe_dict(data, 0, {})
|
||
return self.parse_trading_fee(data, market)
|
||
|
||
def fetch_trading_fees(self, params={}) -> TradingFees:
|
||
"""
|
||
fetch the trading fees for multiple markets
|
||
|
||
https://developers.binance.com/docs/wallet/asset/trade-fee
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V2
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Account-Information
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Config
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchTradingFees', None, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchTradingFees', None, params, 'linear')
|
||
isSpotOrMargin = (type == 'spot') or (type == 'margin')
|
||
isLinear = self.is_linear(type, subType)
|
||
isInverse = self.is_inverse(type, subType)
|
||
response = None
|
||
if isSpotOrMargin:
|
||
response = self.sapiGetAssetTradeFee(params)
|
||
elif isLinear:
|
||
response = self.fapiPrivateGetAccountConfig(params)
|
||
elif isInverse:
|
||
response = self.dapiPrivateGetAccount(params)
|
||
#
|
||
# sapi / spot
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ZRXBNB",
|
||
# "makerCommission": "0.001",
|
||
# "takerCommission": "0.001"
|
||
# },
|
||
# {
|
||
# "symbol": "ZRXBTC",
|
||
# "makerCommission": "0.001",
|
||
# "takerCommission": "0.001"
|
||
# },
|
||
# ]
|
||
#
|
||
# fapi / future / linear
|
||
#
|
||
# {
|
||
# "feeTier": 0, # account commisssion tier
|
||
# "canTrade": True, # if can trade
|
||
# "canDeposit": True, # if can transfer in asset
|
||
# "canWithdraw": True, # if can transfer out asset
|
||
# "updateTime": 0,
|
||
# "totalInitialMargin": "0.00000000", # total initial margin required with current mark price(useless with isolated positions), only for USDT asset
|
||
# "totalMaintMargin": "0.00000000", # total maintenance margin required, only for USDT asset
|
||
# "totalWalletBalance": "23.72469206", # total wallet balance, only for USDT asset
|
||
# "totalUnrealizedProfit": "0.00000000", # total unrealized profit, only for USDT asset
|
||
# "totalMarginBalance": "23.72469206", # total margin balance, only for USDT asset
|
||
# "totalPositionInitialMargin": "0.00000000", # initial margin required for positions with current mark price, only for USDT asset
|
||
# "totalOpenOrderInitialMargin": "0.00000000", # initial margin required for open orders with current mark price, only for USDT asset
|
||
# "totalCrossWalletBalance": "23.72469206", # crossed wallet balance, only for USDT asset
|
||
# "totalCrossUnPnl": "0.00000000", # unrealized profit of crossed positions, only for USDT asset
|
||
# "availableBalance": "23.72469206", # available balance, only for USDT asset
|
||
# "maxWithdrawAmount": "23.72469206" # maximum amount for transfer out, only for USDT asset
|
||
# ...
|
||
# }
|
||
#
|
||
# dapi / delivery / inverse
|
||
#
|
||
# {
|
||
# "canDeposit": True,
|
||
# "canTrade": True,
|
||
# "canWithdraw": True,
|
||
# "feeTier": 2,
|
||
# "updateTime": 0
|
||
# }
|
||
#
|
||
if isSpotOrMargin:
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ZRXBNB",
|
||
# "makerCommission": "0.001",
|
||
# "takerCommission": "0.001"
|
||
# },
|
||
# {
|
||
# "symbol": "ZRXBTC",
|
||
# "makerCommission": "0.001",
|
||
# "takerCommission": "0.001"
|
||
# },
|
||
# ]
|
||
#
|
||
result: dict = {}
|
||
for i in range(0, len(response)):
|
||
fee = self.parse_trading_fee(response[i])
|
||
symbol = fee['symbol']
|
||
result[symbol] = fee
|
||
return result
|
||
elif isLinear:
|
||
#
|
||
# {
|
||
# "feeTier": 0, # account commisssion tier
|
||
# "canTrade": True, # if can trade
|
||
# "canDeposit": True, # if can transfer in asset
|
||
# "canWithdraw": True, # if can transfer out asset
|
||
# "updateTime": 0,
|
||
# "totalInitialMargin": "0.00000000", # total initial margin required with current mark price(useless with isolated positions), only for USDT asset
|
||
# "totalMaintMargin": "0.00000000", # total maintenance margin required, only for USDT asset
|
||
# "totalWalletBalance": "23.72469206", # total wallet balance, only for USDT asset
|
||
# "totalUnrealizedProfit": "0.00000000", # total unrealized profit, only for USDT asset
|
||
# "totalMarginBalance": "23.72469206", # total margin balance, only for USDT asset
|
||
# "totalPositionInitialMargin": "0.00000000", # initial margin required for positions with current mark price, only for USDT asset
|
||
# "totalOpenOrderInitialMargin": "0.00000000", # initial margin required for open orders with current mark price, only for USDT asset
|
||
# "totalCrossWalletBalance": "23.72469206", # crossed wallet balance, only for USDT asset
|
||
# "totalCrossUnPnl": "0.00000000", # unrealized profit of crossed positions, only for USDT asset
|
||
# "availableBalance": "23.72469206", # available balance, only for USDT asset
|
||
# "maxWithdrawAmount": "23.72469206" # maximum amount for transfer out, only for USDT asset
|
||
# ...
|
||
# }
|
||
#
|
||
symbols = list(self.markets.keys())
|
||
result: dict = {}
|
||
feeTier = self.safe_integer(response, 'feeTier')
|
||
feeTiers = self.fees['linear']['trading']['tiers']
|
||
maker = feeTiers['maker'][feeTier][1]
|
||
taker = feeTiers['taker'][feeTier][1]
|
||
for i in range(0, len(symbols)):
|
||
symbol = symbols[i]
|
||
market = self.markets[symbol]
|
||
if market['linear']:
|
||
result[symbol] = {
|
||
'info': {
|
||
'feeTier': feeTier,
|
||
},
|
||
'symbol': symbol,
|
||
'maker': maker,
|
||
'taker': taker,
|
||
}
|
||
return result
|
||
elif isInverse:
|
||
#
|
||
# {
|
||
# "canDeposit": True,
|
||
# "canTrade": True,
|
||
# "canWithdraw": True,
|
||
# "feeTier": 2,
|
||
# "updateTime": 0
|
||
# }
|
||
#
|
||
symbols = list(self.markets.keys())
|
||
result: dict = {}
|
||
feeTier = self.safe_integer(response, 'feeTier')
|
||
feeTiers = self.fees['inverse']['trading']['tiers']
|
||
maker = feeTiers['maker'][feeTier][1]
|
||
taker = feeTiers['taker'][feeTier][1]
|
||
for i in range(0, len(symbols)):
|
||
symbol = symbols[i]
|
||
market = self.markets[symbol]
|
||
if market['inverse']:
|
||
result[symbol] = {
|
||
'info': {
|
||
'feeTier': feeTier,
|
||
},
|
||
'symbol': symbol,
|
||
'maker': maker,
|
||
'taker': taker,
|
||
}
|
||
return result
|
||
return None
|
||
|
||
def futures_transfer(self, code: str, amount, type, params={}):
|
||
"""
|
||
@ignore
|
||
transfer between futures account
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/New-Future-Account-Transfer
|
||
|
||
:param str code: unified currency code
|
||
:param float amount: the amount to transfer
|
||
:param str type: 1 - transfer from spot account to USDT-Ⓜ futures account, 2 - transfer from USDT-Ⓜ futures account to spot account, 3 - transfer from spot account to COIN-Ⓜ futures account, 4 - transfer from COIN-Ⓜ futures account to spot account
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param float params.recvWindow:
|
||
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=futures-transfer-structure>`
|
||
"""
|
||
if (type < 1) or (type > 4):
|
||
raise ArgumentsRequired(self.id + ' type must be between 1 and 4')
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
'amount': amount,
|
||
'type': type,
|
||
}
|
||
response = self.sapiPostFuturesTransfer(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId": 100000001
|
||
# }
|
||
#
|
||
return self.parse_transfer(response, currency)
|
||
|
||
def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
|
||
"""
|
||
fetch the current funding rate
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Mark-Price
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Index-Price-and-Mark-Price
|
||
|
||
: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['linear']:
|
||
response = self.fapiPublicGetPremiumIndex(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPublicGetPremiumIndex(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchFundingRate() supports linear and inverse contracts only')
|
||
if market['inverse']:
|
||
response = response[0]
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "markPrice": "45802.81129892",
|
||
# "indexPrice": "45745.47701915",
|
||
# "estimatedSettlePrice": "45133.91753671",
|
||
# "lastFundingRate": "0.00063521",
|
||
# "interestRate": "0.00010000",
|
||
# "nextFundingTime": "1621267200000",
|
||
# "time": "1621252344001"
|
||
# }
|
||
#
|
||
return self.parse_funding_rate(response, market)
|
||
|
||
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical funding rate prices
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Get-Funding-Rate-History
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Get-Funding-Rate-History-of-Perpetual-Futures
|
||
|
||
: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
|
||
: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)
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
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)
|
||
defaultType = self.safe_string_2(self.options, 'fetchFundingRateHistory', 'defaultType', 'future')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
symbol = market['symbol']
|
||
request['symbol'] = market['id']
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchFundingRateHistory', market, params, 'linear')
|
||
params = self.omit(params, 'type')
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until = self.safe_integer(params, 'until') # unified in milliseconds
|
||
endTime = self.safe_integer(params, 'endTime', until) # exchange-specific in milliseconds
|
||
params = self.omit(params, ['endTime', 'until'])
|
||
if endTime is not None:
|
||
request['endTime'] = endTime
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicGetFundingRate(self.extend(request, params))
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetFundingRate(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchFundingRateHistory() is not supported for ' + type + ' markets')
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "fundingRate": "0.00063521",
|
||
# "fundingTime": "1621267200000",
|
||
# }
|
||
#
|
||
return self.parse_funding_rate_histories(response, market, since, limit)
|
||
|
||
def parse_funding_rate_history(self, contract, market: Market = None):
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "fundingRate": "0.00063521",
|
||
# "fundingTime": "1621267200000",
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(contract, 'fundingTime')
|
||
return {
|
||
'info': contract,
|
||
'symbol': self.safe_symbol(self.safe_string(contract, 'symbol'), None, None, 'swap'),
|
||
'fundingRate': self.safe_number(contract, 'fundingRate'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
|
||
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
|
||
"""
|
||
fetch the funding rate for multiple markets
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Mark-Price
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Index-Price-and-Mark-Price
|
||
|
||
:param str[]|None symbols: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rates-structure>`, indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols)
|
||
defaultType = self.safe_string_2(self.options, 'fetchFundingRates', 'defaultType', 'future')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchFundingRates', None, params, 'linear')
|
||
query = self.omit(params, 'type')
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicGetPremiumIndex(query)
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetPremiumIndex(query)
|
||
else:
|
||
raise NotSupported(self.id + ' fetchFundingRates() supports linear and inverse contracts only')
|
||
return self.parse_funding_rates(response, symbols)
|
||
|
||
def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
|
||
# ensure it matches with https://www.binance.com/en/futures/funding-history/0
|
||
#
|
||
# fetchFundingRate, fetchFundingRates
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "markPrice": "45802.81129892",
|
||
# "indexPrice": "45745.47701915",
|
||
# "estimatedSettlePrice": "45133.91753671",
|
||
# "lastFundingRate": "0.00063521",
|
||
# "interestRate": "0.00010000",
|
||
# "nextFundingTime": "1621267200000",
|
||
# "time": "1621252344001"
|
||
# }
|
||
#
|
||
# fetchFundingInterval, fetchFundingIntervals
|
||
#
|
||
# {
|
||
# "symbol": "BLZUSDT",
|
||
# "adjustedFundingRateCap": "0.03000000",
|
||
# "adjustedFundingRateFloor": "-0.03000000",
|
||
# "fundingIntervalHours": 4,
|
||
# "disclaimer": False
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(contract, 'time')
|
||
marketId = self.safe_string(contract, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market, None, 'contract')
|
||
markPrice = self.safe_number(contract, 'markPrice')
|
||
indexPrice = self.safe_number(contract, 'indexPrice')
|
||
interestRate = self.safe_number(contract, 'interestRate')
|
||
estimatedSettlePrice = self.safe_number(contract, 'estimatedSettlePrice')
|
||
fundingRate = self.safe_number(contract, 'lastFundingRate')
|
||
fundingTime = self.safe_integer(contract, 'nextFundingTime')
|
||
interval = self.safe_string(contract, 'fundingIntervalHours')
|
||
intervalString = None
|
||
if interval is not None:
|
||
intervalString = interval + 'h'
|
||
return {
|
||
'info': contract,
|
||
'symbol': symbol,
|
||
'markPrice': markPrice,
|
||
'indexPrice': indexPrice,
|
||
'interestRate': interestRate,
|
||
'estimatedSettlePrice': estimatedSettlePrice,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'fundingRate': fundingRate,
|
||
'fundingTimestamp': fundingTime,
|
||
'fundingDatetime': self.iso8601(fundingTime),
|
||
'nextFundingRate': None,
|
||
'nextFundingTimestamp': None,
|
||
'nextFundingDatetime': None,
|
||
'previousFundingRate': None,
|
||
'previousFundingTimestamp': None,
|
||
'previousFundingDatetime': None,
|
||
'interval': intervalString,
|
||
}
|
||
|
||
def parse_account_positions(self, account, filterClosed=False):
|
||
positions = self.safe_list(account, 'positions')
|
||
assets = self.safe_list(account, 'assets', [])
|
||
balances: dict = {}
|
||
for i in range(0, len(assets)):
|
||
entry = assets[i]
|
||
currencyId = self.safe_string(entry, 'asset')
|
||
code = self.safe_currency_code(currencyId)
|
||
crossWalletBalance = self.safe_string(entry, 'crossWalletBalance')
|
||
crossUnPnl = self.safe_string(entry, 'crossUnPnl')
|
||
balances[code] = {
|
||
'crossMargin': Precise.string_add(crossWalletBalance, crossUnPnl),
|
||
'crossWalletBalance': crossWalletBalance,
|
||
}
|
||
result = []
|
||
for i in range(0, len(positions)):
|
||
position = positions[i]
|
||
marketId = self.safe_string(position, 'symbol')
|
||
market = self.safe_market(marketId, None, None, 'contract')
|
||
code = market['quote'] if market['linear'] else market['base']
|
||
maintenanceMargin = self.safe_string(position, 'maintMargin')
|
||
# check for maintenance margin so empty positions are not returned
|
||
isPositionOpen = (maintenanceMargin != '0') and (maintenanceMargin != '0.00000000')
|
||
if not filterClosed or isPositionOpen:
|
||
# sometimes not all the codes are correctly returned...
|
||
if code in balances:
|
||
parsed = self.parse_account_position(self.extend(position, {
|
||
'crossMargin': balances[code]['crossMargin'],
|
||
'crossWalletBalance': balances[code]['crossWalletBalance'],
|
||
}), market)
|
||
result.append(parsed)
|
||
return result
|
||
|
||
def parse_account_position(self, position, market: Market = None):
|
||
#
|
||
# usdm
|
||
#
|
||
# v3(similar for cross & isolated)
|
||
#
|
||
# {
|
||
# "symbol": "WLDUSDT",
|
||
# "positionSide": "BOTH",
|
||
# "positionAmt": "-849",
|
||
# "unrealizedProfit": "11.17920750",
|
||
# "notional": "-1992.46079250",
|
||
# "isolatedMargin": "0",
|
||
# "isolatedWallet": "0",
|
||
# "initialMargin": "99.62303962",
|
||
# "maintMargin": "11.95476475",
|
||
# "updateTime": "1721995760449"
|
||
# "leverage": "50", # in v2
|
||
# "entryPrice": "2.34", # in v2
|
||
# "positionInitialMargin": "118.82116614", # in v2
|
||
# "openOrderInitialMargin": "0", # in v2
|
||
# "isolated": False, # in v2
|
||
# "breakEvenPrice": "2.3395788", # in v2
|
||
# "maxNotional": "25000", # in v2
|
||
# "bidNotional": "0", # in v2
|
||
# "askNotional": "0" # in v2
|
||
# }
|
||
#
|
||
# coinm
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSD_210625",
|
||
# "initialMargin": "0.00024393",
|
||
# "maintMargin": "0.00002439",
|
||
# "unrealizedProfit": "-0.00000163",
|
||
# "positionInitialMargin": "0.00024393",
|
||
# "openOrderInitialMargin": "0",
|
||
# "leverage": "10",
|
||
# "isolated": False,
|
||
# "positionSide": "BOTH",
|
||
# "entryPrice": "41021.20000069",
|
||
# "maxQty": "100",
|
||
# "notionalValue": "0.00243939",
|
||
# "isolatedWallet": "0",
|
||
# "crossMargin": "0.314"
|
||
# "crossWalletBalance": "34",
|
||
# }
|
||
#
|
||
# linear portfolio margin
|
||
#
|
||
# {
|
||
# "symbol": "CTSIUSDT",
|
||
# "initialMargin": "0",
|
||
# "maintMargin": "0",
|
||
# "unrealizedProfit": "0.00000000",
|
||
# "positionInitialMargin": "0",
|
||
# "openOrderInitialMargin": "0",
|
||
# "leverage": "20",
|
||
# "entryPrice": "0.0",
|
||
# "maxNotional": "25000",
|
||
# "bidNotional": "0",
|
||
# "askNotional": "0",
|
||
# "positionSide": "SHORT",
|
||
# "positionAmt": "0",
|
||
# "updateTime": 0,
|
||
# "notional": "0",
|
||
# "breakEvenPrice": "0.0"
|
||
# }
|
||
#
|
||
# inverse portoflio margin
|
||
#
|
||
# {
|
||
# "symbol": "TRXUSD_PERP",
|
||
# "initialMargin": "0",
|
||
# "maintMargin": "0",
|
||
# "unrealizedProfit": "0.00000000",
|
||
# "positionInitialMargin": "0",
|
||
# "openOrderInitialMargin": "0",
|
||
# "leverage": "20",
|
||
# "entryPrice": "0.00000000",
|
||
# "positionSide": "SHORT",
|
||
# "positionAmt": "0",
|
||
# "maxQty": "5000000",
|
||
# "updateTime": 0,
|
||
# "notionalValue": "0",
|
||
# "breakEvenPrice": "0.00000000"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(position, 'symbol')
|
||
market = self.safe_market(marketId, market, None, 'contract')
|
||
symbol = self.safe_string(market, 'symbol')
|
||
leverageString = self.safe_string(position, 'leverage')
|
||
leverage = int(leverageString) if (leverageString is not None) else None
|
||
initialMarginString = self.safe_string(position, 'initialMargin')
|
||
initialMargin = self.parse_number(initialMarginString)
|
||
initialMarginPercentageString = None
|
||
if leverageString is not None:
|
||
initialMarginPercentageString = Precise.string_div('1', leverageString, 8)
|
||
rational = self.is_round_number(1000 % leverage)
|
||
if not rational:
|
||
initialMarginPercentageString = Precise.string_div(Precise.string_add(initialMarginPercentageString, '1e-8'), '1', 8)
|
||
# to notionalValue
|
||
usdm = ('notional' in position)
|
||
maintenanceMarginString = self.safe_string(position, 'maintMargin')
|
||
maintenanceMargin = self.parse_number(maintenanceMarginString)
|
||
entryPriceString = self.safe_string(position, 'entryPrice')
|
||
entryPrice = self.parse_number(entryPriceString)
|
||
notionalString = self.safe_string_2(position, 'notional', 'notionalValue')
|
||
notionalStringAbs = Precise.string_abs(notionalString)
|
||
notional = self.parse_number(notionalStringAbs)
|
||
contractsString = self.safe_string(position, 'positionAmt')
|
||
contractsStringAbs = Precise.string_abs(contractsString)
|
||
if contractsString is None:
|
||
entryNotional = Precise.string_mul(Precise.string_mul(leverageString, initialMarginString), entryPriceString)
|
||
contractSizeNew = self.safe_string(market, 'contractSize')
|
||
contractsString = Precise.string_div(entryNotional, contractSizeNew)
|
||
contractsStringAbs = Precise.string_div(Precise.string_add(contractsString, '0.5'), '1', 0)
|
||
contracts = self.parse_number(contractsStringAbs)
|
||
leverageBrackets = self.safe_dict(self.options, 'leverageBrackets', {})
|
||
leverageBracket = self.safe_list(leverageBrackets, symbol, [])
|
||
maintenanceMarginPercentageString = None
|
||
for i in range(0, len(leverageBracket)):
|
||
bracket = leverageBracket[i]
|
||
if Precise.string_lt(notionalStringAbs, bracket[0]):
|
||
break
|
||
maintenanceMarginPercentageString = bracket[1]
|
||
maintenanceMarginPercentage = self.parse_number(maintenanceMarginPercentageString)
|
||
unrealizedPnlString = self.safe_string(position, 'unrealizedProfit')
|
||
unrealizedPnl = self.parse_number(unrealizedPnlString)
|
||
timestamp = self.safe_integer(position, 'updateTime')
|
||
if timestamp == 0:
|
||
timestamp = None
|
||
isolated = self.safe_bool(position, 'isolated')
|
||
if isolated is None:
|
||
isolatedMarginRaw = self.safe_string(position, 'isolatedMargin')
|
||
isolated = not Precise.string_eq(isolatedMarginRaw, '0')
|
||
marginMode = None
|
||
collateralString = None
|
||
walletBalance = None
|
||
if isolated:
|
||
marginMode = 'isolated'
|
||
walletBalance = self.safe_string(position, 'isolatedWallet')
|
||
collateralString = Precise.string_add(walletBalance, unrealizedPnlString)
|
||
else:
|
||
marginMode = 'cross'
|
||
walletBalance = self.safe_string(position, 'crossWalletBalance')
|
||
collateralString = self.safe_string(position, 'crossMargin')
|
||
collateral = self.parse_number(collateralString)
|
||
marginRatio = None
|
||
side = None
|
||
percentage = None
|
||
liquidationPriceStringRaw = None
|
||
liquidationPrice = None
|
||
contractSize = self.safe_value(market, 'contractSize')
|
||
contractSizeString = self.number_to_string(contractSize)
|
||
if Precise.string_equals(notionalString, '0'):
|
||
entryPrice = None
|
||
else:
|
||
side = 'short' if Precise.string_lt(notionalString, '0') else 'long'
|
||
marginRatio = self.parse_number(Precise.string_div(Precise.string_add(Precise.string_div(maintenanceMarginString, collateralString), '5e-5'), '1', 4))
|
||
percentage = self.parse_number(Precise.string_mul(Precise.string_div(unrealizedPnlString, initialMarginString, 4), '100'))
|
||
if usdm:
|
||
# calculate liquidation price
|
||
#
|
||
# liquidationPrice = (walletBalance / (contracts * (±1 + mmp))) + (±entryPrice / (±1 + mmp))
|
||
#
|
||
# mmp = maintenanceMarginPercentage
|
||
# where ± is negative for long and positive for short
|
||
# TODO: calculate liquidation price for coinm contracts
|
||
onePlusMaintenanceMarginPercentageString = None
|
||
entryPriceSignString = entryPriceString
|
||
if side == 'short':
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_add('1', maintenanceMarginPercentageString)
|
||
else:
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_add('-1', maintenanceMarginPercentageString)
|
||
entryPriceSignString = Precise.string_mul('-1', entryPriceSignString)
|
||
leftSide = Precise.string_div(walletBalance, Precise.string_mul(contractsStringAbs, onePlusMaintenanceMarginPercentageString))
|
||
rightSide = Precise.string_div(entryPriceSignString, onePlusMaintenanceMarginPercentageString)
|
||
liquidationPriceStringRaw = Precise.string_add(leftSide, rightSide)
|
||
else:
|
||
# calculate liquidation price
|
||
#
|
||
# liquidationPrice = (contracts * contractSize(±1 - mmp)) / (±1/entryPrice * contracts * contractSize - walletBalance)
|
||
#
|
||
onePlusMaintenanceMarginPercentageString = None
|
||
entryPriceSignString = entryPriceString
|
||
if side == 'short':
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_sub('1', maintenanceMarginPercentageString)
|
||
else:
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_sub('-1', maintenanceMarginPercentageString)
|
||
entryPriceSignString = Precise.string_mul('-1', entryPriceSignString)
|
||
size = Precise.string_mul(contractsStringAbs, contractSizeString)
|
||
leftSide = Precise.string_mul(size, onePlusMaintenanceMarginPercentageString)
|
||
rightSide = Precise.string_sub(Precise.string_mul(Precise.string_div('1', entryPriceSignString), size), walletBalance)
|
||
liquidationPriceStringRaw = Precise.string_div(leftSide, rightSide)
|
||
pricePrecision = self.precision_from_string(self.safe_string(market['precision'], 'price'))
|
||
pricePrecisionPlusOne = pricePrecision + 1
|
||
pricePrecisionPlusOneString = str(pricePrecisionPlusOne)
|
||
# round half up
|
||
rounder = Precise('5e-' + pricePrecisionPlusOneString)
|
||
rounderString = str(rounder)
|
||
liquidationPriceRoundedString = Precise.string_add(rounderString, liquidationPriceStringRaw)
|
||
truncatedLiquidationPrice = Precise.string_div(liquidationPriceRoundedString, '1', pricePrecision)
|
||
if truncatedLiquidationPrice[0] == '-':
|
||
# user cannot be liquidated
|
||
# since he has more collateral than the size of the position
|
||
truncatedLiquidationPrice = None
|
||
liquidationPrice = self.parse_number(truncatedLiquidationPrice)
|
||
positionSide = self.safe_string(position, 'positionSide')
|
||
hedged = positionSide != 'BOTH'
|
||
return {
|
||
'info': position,
|
||
'id': None,
|
||
'symbol': symbol,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'initialMargin': initialMargin,
|
||
'initialMarginPercentage': self.parse_number(initialMarginPercentageString),
|
||
'maintenanceMargin': maintenanceMargin,
|
||
'maintenanceMarginPercentage': maintenanceMarginPercentage,
|
||
'entryPrice': entryPrice,
|
||
'notional': notional,
|
||
'leverage': self.parse_number(leverageString),
|
||
'unrealizedPnl': unrealizedPnl,
|
||
'contracts': contracts,
|
||
'contractSize': contractSize,
|
||
'marginRatio': marginRatio,
|
||
'liquidationPrice': liquidationPrice,
|
||
'markPrice': None,
|
||
'collateral': collateral,
|
||
'marginMode': marginMode,
|
||
'side': side,
|
||
'hedged': hedged,
|
||
'percentage': percentage,
|
||
}
|
||
|
||
def parse_position_risk(self, position, market: Market = None):
|
||
#
|
||
# usdm
|
||
#
|
||
# {
|
||
# symbol: "WLDUSDT",
|
||
# positionSide: "BOTH",
|
||
# positionAmt: "5",
|
||
# entryPrice: "2.3483",
|
||
# breakEvenPrice: "2.349356735",
|
||
# markPrice: "2.39560000",
|
||
# unRealizedProfit: "0.23650000",
|
||
# liquidationPrice: "0",
|
||
# isolatedMargin: "0",
|
||
# notional: "11.97800000",
|
||
# isolatedWallet: "0",
|
||
# updateTime: "1722062678998",
|
||
# initialMargin: "2.39560000", # not in v2
|
||
# maintMargin: "0.07186800", # not in v2
|
||
# positionInitialMargin: "2.39560000", # not in v2
|
||
# openOrderInitialMargin: "0", # not in v2
|
||
# adl: "2", # not in v2
|
||
# bidNotional: "0", # not in v2
|
||
# askNotional: "0", # not in v2
|
||
# marginAsset: "USDT", # not in v2
|
||
# # the below fields are only in v2
|
||
# leverage: "5",
|
||
# maxNotionalValue: "6000000",
|
||
# marginType: "cross",
|
||
# isAutoAddMargin: "false",
|
||
# isolated: False,
|
||
# adlQuantile: "2",
|
||
#
|
||
# coinm
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSD_PERP",
|
||
# "positionAmt": "2",
|
||
# "entryPrice": "37643.10000021",
|
||
# "markPrice": "38103.05510455",
|
||
# "unRealizedProfit": "0.00006413",
|
||
# "liquidationPrice": "25119.97445760",
|
||
# "leverage": "2",
|
||
# "maxQty": "1500",
|
||
# "marginType": "isolated",
|
||
# "isolatedMargin": "0.00274471",
|
||
# "isAutoAddMargin": "false",
|
||
# "positionSide": "BOTH",
|
||
# "notionalValue": "0.00524892",
|
||
# "isolatedWallet": "0.00268058"
|
||
# }
|
||
#
|
||
# inverse portfolio margin
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "positionAmt": "1",
|
||
# "entryPrice": "2422.400000007",
|
||
# "markPrice": "2424.51267823",
|
||
# "unRealizedProfit": "0.0000036",
|
||
# "liquidationPrice": "293.57678898",
|
||
# "leverage": "100",
|
||
# "positionSide": "LONG",
|
||
# "updateTime": 1707371941861,
|
||
# "maxQty": "15",
|
||
# "notionalValue": "0.00412454",
|
||
# "breakEvenPrice": "2423.368960034"
|
||
# }
|
||
#
|
||
# linear portfolio margin
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "positionAmt": "0.01",
|
||
# "entryPrice": "44525.0",
|
||
# "markPrice": "45464.1735922",
|
||
# "unRealizedProfit": "9.39173592",
|
||
# "liquidationPrice": "38007.16308568",
|
||
# "leverage": "100",
|
||
# "positionSide": "LONG",
|
||
# "updateTime": 1707371879042,
|
||
# "maxNotionalValue": "500000.0",
|
||
# "notional": "454.64173592",
|
||
# "breakEvenPrice": "44542.81"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(position, 'symbol')
|
||
market = self.safe_market(marketId, market, None, 'contract')
|
||
symbol = self.safe_string(market, 'symbol')
|
||
isolatedMarginString = self.safe_string(position, 'isolatedMargin')
|
||
leverageBrackets = self.safe_dict(self.options, 'leverageBrackets', {})
|
||
leverageBracket = self.safe_list(leverageBrackets, symbol, [])
|
||
notionalString = self.safe_string_2(position, 'notional', 'notionalValue')
|
||
notionalStringAbs = Precise.string_abs(notionalString)
|
||
maintenanceMarginPercentageString = None
|
||
for i in range(0, len(leverageBracket)):
|
||
bracket = leverageBracket[i]
|
||
if Precise.string_lt(notionalStringAbs, bracket[0]):
|
||
break
|
||
maintenanceMarginPercentageString = bracket[1]
|
||
notional = self.parse_number(notionalStringAbs)
|
||
contractsAbs = Precise.string_abs(self.safe_string(position, 'positionAmt'))
|
||
contracts = self.parse_number(contractsAbs)
|
||
unrealizedPnlString = self.safe_string(position, 'unRealizedProfit')
|
||
unrealizedPnl = self.parse_number(unrealizedPnlString)
|
||
liquidationPriceString = self.omit_zero(self.safe_string(position, 'liquidationPrice'))
|
||
liquidationPrice = self.parse_number(liquidationPriceString)
|
||
collateralString = None
|
||
marginMode = self.safe_string(position, 'marginType')
|
||
if marginMode is None and isolatedMarginString is not None:
|
||
marginMode = 'cross' if Precise.string_eq(isolatedMarginString, '0') else 'isolated'
|
||
side = None
|
||
if Precise.string_gt(notionalString, '0'):
|
||
side = 'long'
|
||
elif Precise.string_lt(notionalString, '0'):
|
||
side = 'short'
|
||
entryPriceString = self.safe_string(position, 'entryPrice')
|
||
entryPrice = self.parse_number(entryPriceString)
|
||
contractSize = self.safe_value(market, 'contractSize')
|
||
contractSizeString = self.number_to_string(contractSize)
|
||
# to notionalValue
|
||
linear = ('notional' in position)
|
||
if marginMode == 'cross':
|
||
# calculate collateral
|
||
precision = self.safe_dict(market, 'precision', {})
|
||
basePrecisionValue = self.safe_string(precision, 'base')
|
||
quotePrecisionValue = self.safe_string_2(precision, 'quote', 'price')
|
||
precisionIsUndefined = (basePrecisionValue is None) and (quotePrecisionValue is None)
|
||
if not precisionIsUndefined:
|
||
if linear:
|
||
# walletBalance = (liquidationPrice * (±1 + mmp) ± entryPrice) * contracts
|
||
onePlusMaintenanceMarginPercentageString = None
|
||
entryPriceSignString = entryPriceString
|
||
if side == 'short':
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_add('1', maintenanceMarginPercentageString)
|
||
entryPriceSignString = Precise.string_mul('-1', entryPriceSignString)
|
||
else:
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_add('-1', maintenanceMarginPercentageString)
|
||
inner = Precise.string_mul(liquidationPriceString, onePlusMaintenanceMarginPercentageString)
|
||
leftSide = Precise.string_add(inner, entryPriceSignString)
|
||
quotePrecision = self.precision_from_string(self.safe_string_2(precision, 'quote', 'price'))
|
||
if quotePrecision is not None:
|
||
collateralString = Precise.string_div(Precise.string_mul(leftSide, contractsAbs), '1', quotePrecision)
|
||
else:
|
||
# walletBalance = (contracts * contractSize) * (±1/entryPrice - (±1 - mmp) / liquidationPrice)
|
||
onePlusMaintenanceMarginPercentageString = None
|
||
entryPriceSignString = entryPriceString
|
||
if side == 'short':
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_sub('1', maintenanceMarginPercentageString)
|
||
else:
|
||
onePlusMaintenanceMarginPercentageString = Precise.string_sub('-1', maintenanceMarginPercentageString)
|
||
entryPriceSignString = Precise.string_mul('-1', entryPriceSignString)
|
||
leftSide = Precise.string_mul(contractsAbs, contractSizeString)
|
||
rightSide = Precise.string_sub(Precise.string_div('1', entryPriceSignString), Precise.string_div(onePlusMaintenanceMarginPercentageString, liquidationPriceString))
|
||
basePrecision = self.precision_from_string(self.safe_string(precision, 'base'))
|
||
if basePrecision is not None:
|
||
collateralString = Precise.string_div(Precise.string_mul(leftSide, rightSide), '1', basePrecision)
|
||
else:
|
||
collateralString = self.safe_string(position, 'isolatedMargin')
|
||
collateralString = '0' if (collateralString is None) else collateralString
|
||
collateral = self.parse_number(collateralString)
|
||
markPrice = self.parse_number(self.omit_zero(self.safe_string(position, 'markPrice')))
|
||
timestamp = self.safe_integer(position, 'updateTime')
|
||
if timestamp == 0:
|
||
timestamp = None
|
||
maintenanceMarginPercentage = self.parse_number(maintenanceMarginPercentageString)
|
||
maintenanceMarginString = Precise.string_mul(maintenanceMarginPercentageString, notionalStringAbs)
|
||
if maintenanceMarginString is None:
|
||
# for a while, self new value was a backup to the existing calculations, but in future we might prioritize self
|
||
maintenanceMarginString = self.safe_string(position, 'maintMargin')
|
||
maintenanceMargin = self.parse_number(maintenanceMarginString)
|
||
initialMarginString = None
|
||
initialMarginPercentageString = None
|
||
leverageString = self.safe_string(position, 'leverage')
|
||
if leverageString is not None:
|
||
leverage = int(leverageString)
|
||
rational = self.is_round_number(1000 % leverage)
|
||
initialMarginPercentageString = Precise.string_div('1', leverageString, 8)
|
||
if not rational:
|
||
initialMarginPercentageString = Precise.string_add(initialMarginPercentageString, '1e-8')
|
||
unrounded = Precise.string_mul(notionalStringAbs, initialMarginPercentageString)
|
||
initialMarginString = Precise.string_div(unrounded, '1', 8)
|
||
else:
|
||
initialMarginString = self.safe_string(position, 'initialMargin')
|
||
unrounded = Precise.string_mul(initialMarginString, '1')
|
||
initialMarginPercentageString = Precise.string_div(unrounded, notionalStringAbs, 8)
|
||
marginRatio = None
|
||
percentage = None
|
||
if not Precise.string_equals(collateralString, '0'):
|
||
marginRatio = self.parse_number(Precise.string_div(Precise.string_add(Precise.string_div(maintenanceMarginString, collateralString), '5e-5'), '1', 4))
|
||
percentage = self.parse_number(Precise.string_mul(Precise.string_div(unrealizedPnlString, initialMarginString, 4), '100'))
|
||
positionSide = self.safe_string(position, 'positionSide')
|
||
hedged = positionSide != 'BOTH'
|
||
return {
|
||
'info': position,
|
||
'id': None,
|
||
'symbol': symbol,
|
||
'contracts': contracts,
|
||
'contractSize': contractSize,
|
||
'unrealizedPnl': unrealizedPnl,
|
||
'leverage': self.parse_number(leverageString),
|
||
'liquidationPrice': liquidationPrice,
|
||
'collateral': collateral,
|
||
'notional': notional,
|
||
'markPrice': markPrice,
|
||
'entryPrice': entryPrice,
|
||
'timestamp': timestamp,
|
||
'initialMargin': self.parse_number(initialMarginString),
|
||
'initialMarginPercentage': self.parse_number(initialMarginPercentageString),
|
||
'maintenanceMargin': maintenanceMargin,
|
||
'maintenanceMarginPercentage': maintenanceMarginPercentage,
|
||
'marginRatio': marginRatio,
|
||
'datetime': self.iso8601(timestamp),
|
||
'marginMode': marginMode,
|
||
'marginType': marginMode, # deprecated
|
||
'side': side,
|
||
'hedged': hedged,
|
||
'percentage': percentage,
|
||
'stopLossPrice': None,
|
||
'takeProfitPrice': None,
|
||
}
|
||
|
||
def load_leverage_brackets(self, reload=False, params={}):
|
||
self.load_markets()
|
||
# by default cache the leverage bracket
|
||
# it contains useful stuff like the maintenance margin and initial margin for positions
|
||
leverageBrackets = self.safe_dict(self.options, 'leverageBrackets')
|
||
if (leverageBrackets is None) or (reload):
|
||
defaultType = self.safe_string(self.options, 'defaultType', 'future')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
query = self.omit(params, 'type')
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('loadLeverageBrackets', None, params, 'linear')
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'loadLeverageBrackets', 'papi', 'portfolioMargin', False)
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmLeverageBracket(query)
|
||
else:
|
||
response = self.fapiPrivateGetLeverageBracket(query)
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmLeverageBracket(query)
|
||
else:
|
||
response = self.dapiPrivateV2GetLeverageBracket(query)
|
||
else:
|
||
raise NotSupported(self.id + ' loadLeverageBrackets() supports linear and inverse contracts only')
|
||
self.options['leverageBrackets'] = self.create_safe_dictionary()
|
||
for i in range(0, len(response)):
|
||
entry = response[i]
|
||
marketId = self.safe_string(entry, 'symbol')
|
||
symbol = self.safe_symbol(marketId, None, None, 'contract')
|
||
brackets = self.safe_list(entry, 'brackets', [])
|
||
result = []
|
||
for j in range(0, len(brackets)):
|
||
bracket = brackets[j]
|
||
floorValue = self.safe_string_2(bracket, 'notionalFloor', 'qtyFloor')
|
||
maintenanceMarginPercentage = self.safe_string(bracket, 'maintMarginRatio')
|
||
result.append([floorValue, maintenanceMarginPercentage])
|
||
self.options['leverageBrackets'][symbol] = result
|
||
return self.options['leverageBrackets']
|
||
|
||
def fetch_leverage_tiers(self, symbols: Strings = None, params={}) -> LeverageTiers:
|
||
"""
|
||
retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Notional-and-Leverage-Brackets
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Notional-Bracket-for-Pair
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/UM-Notional-and-Leverage-Brackets
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/CM-Notional-and-Leverage-Brackets
|
||
|
||
:param str[]|None symbols: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch the leverage tiers for a portfolio margin account
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a dictionary of `leverage tiers structures <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, indexed by market symbols
|
||
"""
|
||
self.load_markets()
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchLeverageTiers', None, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchLeverageTiers', None, params, 'linear')
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchLeverageTiers', 'papi', 'portfolioMargin', False)
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmLeverageBracket(params)
|
||
else:
|
||
response = self.fapiPrivateGetLeverageBracket(params)
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmLeverageBracket(params)
|
||
else:
|
||
response = self.dapiPrivateV2GetLeverageBracket(params)
|
||
else:
|
||
raise NotSupported(self.id + ' fetchLeverageTiers() supports linear and inverse contracts only')
|
||
#
|
||
# usdm
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "SUSHIUSDT",
|
||
# "brackets": [
|
||
# {
|
||
# "bracket": 1,
|
||
# "initialLeverage": 50,
|
||
# "notionalCap": 50000,
|
||
# "notionalFloor": 0,
|
||
# "maintMarginRatio": 0.01,
|
||
# "cum": 0.0
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
# ]
|
||
#
|
||
# coinm
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol":"XRPUSD_210326",
|
||
# "brackets":[
|
||
# {
|
||
# "bracket":1,
|
||
# "initialLeverage":20,
|
||
# "qtyCap":500000,
|
||
# "qtyFloor":0,
|
||
# "maintMarginRatio":0.0185,
|
||
# "cum":0.0
|
||
# }
|
||
# ]
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_leverage_tiers(response, symbols, 'symbol')
|
||
|
||
def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]:
|
||
"""
|
||
@ignore
|
||
:param dict info: Exchange response for 1 market
|
||
:param dict market: CCXT market
|
||
"""
|
||
#
|
||
# {
|
||
# "symbol": "SUSHIUSDT",
|
||
# "brackets": [
|
||
# {
|
||
# "bracket": 1,
|
||
# "initialLeverage": 50,
|
||
# "notionalCap": 50000,
|
||
# "notionalFloor": 0,
|
||
# "maintMarginRatio": 0.01,
|
||
# "cum": 0.0
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
marketId = self.safe_string(info, 'symbol')
|
||
market = self.safe_market(marketId, market, None, 'contract')
|
||
brackets = self.safe_list(info, 'brackets', [])
|
||
tiers = []
|
||
for j in range(0, len(brackets)):
|
||
bracket = brackets[j]
|
||
tiers.append({
|
||
'tier': self.safe_number(bracket, 'bracket'),
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'currency': market['quote'],
|
||
'minNotional': self.safe_number_2(bracket, 'notionalFloor', 'qtyFloor'),
|
||
'maxNotional': self.safe_number_2(bracket, 'notionalCap', 'qtyCap'),
|
||
'maintenanceMarginRate': self.safe_number(bracket, 'maintMarginRatio'),
|
||
'maxLeverage': self.safe_number(bracket, 'initialLeverage'),
|
||
'info': bracket,
|
||
})
|
||
return tiers
|
||
|
||
def fetch_position(self, symbol: str, params={}):
|
||
"""
|
||
fetch data on an open position
|
||
|
||
https://developers.binance.com/docs/derivatives/option/trade/Option-Position-Information
|
||
|
||
: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['option']:
|
||
raise NotSupported(self.id + ' fetchPosition() supports option markets only')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = self.eapiPrivateGetPosition(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "entryPrice": "27.70000000",
|
||
# "symbol": "ETH-230426-1850-C",
|
||
# "side": "LONG",
|
||
# "quantity": "0.50000000",
|
||
# "reducibleQty": "0.50000000",
|
||
# "markValue": "10.250000000",
|
||
# "ror": "-0.2599",
|
||
# "unrealizedPNL": "-3.600000000",
|
||
# "markPrice": "20.5",
|
||
# "strikePrice": "1850.00000000",
|
||
# "positionCost": "13.85000000",
|
||
# "expiryDate": 1682496000000,
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "quoteAsset": "USDT",
|
||
# "time": 1682492427106
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_position(response[0], market)
|
||
|
||
def fetch_option_positions(self, symbols: Strings = None, params={}):
|
||
"""
|
||
fetch data on open options positions
|
||
|
||
https://developers.binance.com/docs/derivatives/option/trade/Option-Position-Information
|
||
|
||
:param str[]|None symbols: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols)
|
||
request: dict = {}
|
||
market = None
|
||
if symbols is not None:
|
||
symbol = None
|
||
if isinstance(symbols, list):
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength > 1:
|
||
raise BadRequest(self.id + ' fetchPositions() symbols argument cannot contain more than 1 symbol')
|
||
symbol = symbols[0]
|
||
else:
|
||
symbol = symbols
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
response = self.eapiPrivateGetPosition(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "entryPrice": "27.70000000",
|
||
# "symbol": "ETH-230426-1850-C",
|
||
# "side": "LONG",
|
||
# "quantity": "0.50000000",
|
||
# "reducibleQty": "0.50000000",
|
||
# "markValue": "10.250000000",
|
||
# "ror": "-0.2599",
|
||
# "unrealizedPNL": "-3.600000000",
|
||
# "markPrice": "20.5",
|
||
# "strikePrice": "1850.00000000",
|
||
# "positionCost": "13.85000000",
|
||
# "expiryDate": 1682496000000,
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "quoteAsset": "USDT",
|
||
# "time": 1682492427106
|
||
# }
|
||
# ]
|
||
#
|
||
result = []
|
||
for i in range(0, len(response)):
|
||
result.append(self.parse_position(response[i], market))
|
||
return self.filter_by_array_positions(result, 'symbol', symbols, False)
|
||
|
||
def parse_position(self, position: dict, market: Market = None):
|
||
#
|
||
# {
|
||
# "entryPrice": "27.70000000",
|
||
# "symbol": "ETH-230426-1850-C",
|
||
# "side": "LONG",
|
||
# "quantity": "0.50000000",
|
||
# "reducibleQty": "0.50000000",
|
||
# "markValue": "10.250000000",
|
||
# "ror": "-0.2599",
|
||
# "unrealizedPNL": "-3.600000000",
|
||
# "markPrice": "20.5",
|
||
# "strikePrice": "1850.00000000",
|
||
# "positionCost": "13.85000000",
|
||
# "expiryDate": 1682496000000,
|
||
# "priceScale": 1,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "quoteAsset": "USDT",
|
||
# "time": 1682492427106
|
||
# }
|
||
#
|
||
marketId = self.safe_string(position, 'symbol')
|
||
market = self.safe_market(marketId, market, None, 'swap')
|
||
symbol = market['symbol']
|
||
side = self.safe_string_lower(position, 'side')
|
||
quantity = self.safe_string(position, 'quantity')
|
||
if side != 'long':
|
||
quantity = Precise.string_mul('-1', quantity)
|
||
timestamp = self.safe_integer(position, 'time')
|
||
return self.safe_position({
|
||
'info': position,
|
||
'id': None,
|
||
'symbol': symbol,
|
||
'entryPrice': self.safe_number(position, 'entryPrice'),
|
||
'markPrice': self.safe_number(position, 'markPrice'),
|
||
'notional': self.safe_number(position, 'markValue'),
|
||
'collateral': self.safe_number(position, 'positionCost'),
|
||
'unrealizedPnl': self.safe_number(position, 'unrealizedPNL'),
|
||
'side': side,
|
||
'contracts': self.parse_number(quantity),
|
||
'contractSize': None,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'hedged': None,
|
||
'maintenanceMargin': None,
|
||
'maintenanceMarginPercentage': None,
|
||
'initialMargin': None,
|
||
'initialMarginPercentage': None,
|
||
'leverage': None,
|
||
'liquidationPrice': None,
|
||
'marginRatio': None,
|
||
'marginMode': None,
|
||
'percentage': None,
|
||
})
|
||
|
||
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
||
"""
|
||
fetch all open positions
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V2
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Account-Information
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Position-Information-V2
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Position-Information
|
||
https://developers.binance.com/docs/derivatives/option/trade/Option-Position-Information
|
||
|
||
:param str[] [symbols]: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param dict [params.params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.method]: method name to call, "positionRisk", "account" or "option", default is "positionRisk"
|
||
:param bool [params.useV2]: set to True if you want to use the obsolete endpoint, where some more additional fields were provided
|
||
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
||
"""
|
||
defaultMethod = None
|
||
defaultMethod, params = self.handle_option_and_params(params, 'fetchPositions', 'method')
|
||
if defaultMethod is None:
|
||
options = self.safe_dict(self.options, 'fetchPositions')
|
||
if options is None:
|
||
defaultMethod = self.safe_string(self.options, 'fetchPositions', 'positionRisk')
|
||
else:
|
||
defaultMethod = 'positionRisk'
|
||
if defaultMethod == 'positionRisk':
|
||
return self.fetch_positions_risk(symbols, params)
|
||
elif defaultMethod == 'account':
|
||
return self.fetch_account_positions(symbols, params)
|
||
elif defaultMethod == 'option':
|
||
return self.fetch_option_positions(symbols, params)
|
||
else:
|
||
raise NotSupported(self.id + '.options["fetchPositions"]["method"] or params["method"] = "' + defaultMethod + '" is invalid, please choose between "account", "positionRisk" and "option"')
|
||
|
||
def fetch_account_positions(self, symbols: Strings = None, params={}):
|
||
"""
|
||
@ignore
|
||
fetch account positions
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V2
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Account-Information
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Position-Information-V2
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Position-Information
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V3
|
||
|
||
:param str[] [symbols]: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch positions in a portfolio margin account
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:param boolean [params.filterClosed]: set to True if you would like to filter out closed positions, default is False
|
||
:param boolean [params.useV2]: set to True if you want to use obsolete endpoint, where some more additional fields were provided
|
||
:returns dict: data on account positions
|
||
"""
|
||
if symbols is not None:
|
||
if not isinstance(symbols, list):
|
||
raise ArgumentsRequired(self.id + ' fetchPositions() requires an array argument for symbols')
|
||
self.load_markets()
|
||
self.load_leverage_brackets(False, params)
|
||
defaultType = self.safe_string(self.options, 'defaultType', 'future')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
params = self.omit(params, 'type')
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchAccountPositions', None, params, 'linear')
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchAccountPositions', 'papi', 'portfolioMargin', False)
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiV2GetUmAccount(params)
|
||
else:
|
||
useV2 = None
|
||
useV2, params = self.handle_option_and_params(params, 'fetchAccountPositions', 'useV2', False)
|
||
if not useV2:
|
||
response = self.fapiPrivateV3GetAccount(params)
|
||
else:
|
||
response = self.fapiPrivateV2GetAccount(params)
|
||
#
|
||
# {
|
||
# "totalInitialMargin": "99.62112386",
|
||
# "totalMaintMargin": "11.95453485",
|
||
# "totalWalletBalance": "99.84331553",
|
||
# "totalUnrealizedProfit": "11.17675690",
|
||
# "totalMarginBalance": "111.02007243",
|
||
# "totalPositionInitialMargin": "99.62112386",
|
||
# "totalOpenOrderInitialMargin": "0.00000000",
|
||
# "totalCrossWalletBalance": "99.84331553",
|
||
# "totalCrossUnPnl": "11.17675690",
|
||
# "availableBalance": "11.39894857",
|
||
# "maxWithdrawAmount": "11.39894857",
|
||
# "feeTier": "0", # in v2
|
||
# "canTrade": True, # in v2
|
||
# "canDeposit": True, # in v2
|
||
# "canWithdraw": True, # in v2
|
||
# "feeBurn": True, # in v2
|
||
# "tradeGroupId": "-1",// in v2
|
||
# "updateTime": "0", # in v2
|
||
# "multiAssetsMargin": True # in v2
|
||
# "assets": [
|
||
# {
|
||
# "asset": "USDT",
|
||
# "walletBalance": "72.72317863",
|
||
# "unrealizedProfit": "11.17920750",
|
||
# "marginBalance": "83.90238613",
|
||
# "maintMargin": "11.95476475",
|
||
# "initialMargin": "99.62303962",
|
||
# "positionInitialMargin": "99.62303962",
|
||
# "openOrderInitialMargin": "0.00000000",
|
||
# "crossWalletBalance": "72.72317863",
|
||
# "crossUnPnl": "11.17920750",
|
||
# "availableBalance": "11.39916777",
|
||
# "maxWithdrawAmount": "11.39916777",
|
||
# "updateTime": "1721995605338",
|
||
# "marginAvailable": True # in v2
|
||
# },
|
||
# ... and some few supported settle currencies: USDC, BTC, ETH, BNB ..
|
||
# ],
|
||
# "positions": [
|
||
# {
|
||
# "symbol": "WLDUSDT",
|
||
# "positionSide": "BOTH",
|
||
# "positionAmt": "-849",
|
||
# "unrealizedProfit": "11.17920750",
|
||
# "isolatedMargin": "0",
|
||
# "isolatedWallet": "0",
|
||
# "notional": "-1992.46079250",
|
||
# "initialMargin": "99.62303962",
|
||
# "maintMargin": "11.95476475",
|
||
# "updateTime": "1721995760449"
|
||
# "leverage": "50", # in v2
|
||
# "entryPrice": "2.34", # in v2
|
||
# "positionInitialMargin": "118.82116614", # in v2
|
||
# "openOrderInitialMargin": "0", # in v2
|
||
# "isolated": False, # in v2
|
||
# "breakEvenPrice": "2.3395788", # in v2
|
||
# "maxNotional": "25000", # in v2
|
||
# "bidNotional": "0", # in v2
|
||
# "askNotional": "0" # in v2
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmAccount(params)
|
||
else:
|
||
response = self.dapiPrivateGetAccount(params)
|
||
else:
|
||
raise NotSupported(self.id + ' fetchPositions() supports linear and inverse contracts only')
|
||
filterClosed = None
|
||
filterClosed, params = self.handle_option_and_params(params, 'fetchAccountPositions', 'filterClosed', False)
|
||
result = self.parse_account_positions(response, filterClosed)
|
||
symbols = self.market_symbols(symbols)
|
||
return self.filter_by_array_positions(result, 'symbol', symbols, False)
|
||
|
||
def fetch_positions_risk(self, symbols: Strings = None, params={}):
|
||
"""
|
||
@ignore
|
||
fetch positions risk
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Position-Information-V2
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Position-Information
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-UM-Position-Information
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Query-CM-Position-Information
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Position-Information-V3
|
||
|
||
:param str[]|None symbols: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch positions for a portfolio margin account
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:param bool [params.useV2]: set to True if you want to use the obsolete endpoint, where some more additional fields were provided
|
||
:returns dict: data on the positions risk
|
||
"""
|
||
if symbols is not None:
|
||
if not isinstance(symbols, list):
|
||
raise ArgumentsRequired(self.id + ' fetchPositionsRisk() requires an array argument for symbols')
|
||
self.load_markets()
|
||
self.load_leverage_brackets(False, params)
|
||
request: dict = {}
|
||
defaultType = 'future'
|
||
defaultType = self.safe_string(self.options, 'defaultType', defaultType)
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchPositionsRisk', None, params, 'linear')
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchPositionsRisk', 'papi', 'portfolioMargin', False)
|
||
params = self.omit(params, 'type')
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmPositionRisk(self.extend(request, params))
|
||
else:
|
||
useV2 = None
|
||
useV2, params = self.handle_option_and_params(params, 'fetchPositionsRisk', 'useV2', False)
|
||
params = self.extend(request, params)
|
||
if not useV2:
|
||
response = self.fapiPrivateV3GetPositionRisk(params)
|
||
else:
|
||
response = self.fapiPrivateV2GetPositionRisk(params)
|
||
#
|
||
# [
|
||
# {
|
||
# symbol: "WLDUSDT",
|
||
# positionSide: "BOTH",
|
||
# positionAmt: "5",
|
||
# entryPrice: "2.3483",
|
||
# breakEvenPrice: "2.349356735",
|
||
# markPrice: "2.39560000",
|
||
# unRealizedProfit: "0.23650000",
|
||
# liquidationPrice: "0",
|
||
# isolatedMargin: "0",
|
||
# notional: "11.97800000",
|
||
# isolatedWallet: "0",
|
||
# updateTime: "1722062678998",
|
||
# initialMargin: "2.39560000", # added in v3
|
||
# maintMargin: "0.07186800", # added in v3
|
||
# positionInitialMargin: "2.39560000", # added in v3
|
||
# openOrderInitialMargin: "0", # added in v3
|
||
# adl: "2", # added in v3
|
||
# bidNotional: "0", # added in v3
|
||
# askNotional: "0", # added in v3
|
||
# marginAsset: "USDT", # added in v3
|
||
# },
|
||
# ]
|
||
#
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmPositionRisk(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetPositionRisk(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchPositionsRisk() supports linear and inverse contracts only')
|
||
# ### Response examples ###
|
||
#
|
||
# For One-way position mode:
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "positionSide": "BOTH",
|
||
# "positionAmt": "0.000",
|
||
# "entryPrice": "0.00000",
|
||
# "markPrice": "6679.50671178",
|
||
# "unRealizedProfit": "0.00000000",
|
||
# "liquidationPrice": "0",
|
||
# "isolatedMargin": "0.00000000",
|
||
# "marginType": "isolated",
|
||
# "isAutoAddMargin": "false",
|
||
# "leverage": "10",
|
||
# "maxNotionalValue": "20000000",
|
||
# "updateTime": 0
|
||
# }
|
||
# ]
|
||
#
|
||
# For Hedge position mode:
|
||
#
|
||
# [
|
||
# {
|
||
# "entryPrice": "6563.66500",
|
||
# "marginType": "isolated",
|
||
# "isAutoAddMargin": "false",
|
||
# "isolatedMargin": "15517.54150468",
|
||
# "leverage": "10",
|
||
# "liquidationPrice": "5930.78",
|
||
# "markPrice": "6679.50671178",
|
||
# "maxNotionalValue": "20000000",
|
||
# "positionSide": "LONG",
|
||
# "positionAmt": "20.000", # negative value for 'SHORT'
|
||
# "symbol": "BTCUSDT",
|
||
# "unRealizedProfit": "2316.83423560"
|
||
# "updateTime": 1625474304765
|
||
# },
|
||
# .. second dict is similar, but with `positionSide: SHORT`
|
||
# ]
|
||
#
|
||
# inverse portfolio margin:
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "positionAmt": "1",
|
||
# "entryPrice": "2422.400000007",
|
||
# "markPrice": "2424.51267823",
|
||
# "unRealizedProfit": "0.0000036",
|
||
# "liquidationPrice": "293.57678898",
|
||
# "leverage": "100",
|
||
# "positionSide": "LONG",
|
||
# "updateTime": 1707371941861,
|
||
# "maxQty": "15",
|
||
# "notionalValue": "0.00412454",
|
||
# "breakEvenPrice": "2423.368960034"
|
||
# }
|
||
# ]
|
||
#
|
||
# linear portfolio margin:
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "positionAmt": "0.01",
|
||
# "entryPrice": "44525.0",
|
||
# "markPrice": "45464.1735922",
|
||
# "unRealizedProfit": "9.39173592",
|
||
# "liquidationPrice": "38007.16308568",
|
||
# "leverage": "100",
|
||
# "positionSide": "LONG",
|
||
# "updateTime": 1707371879042,
|
||
# "maxNotionalValue": "500000.0",
|
||
# "notional": "454.64173592",
|
||
# "breakEvenPrice": "44542.81"
|
||
# }
|
||
# ]
|
||
#
|
||
result = []
|
||
for i in range(0, len(response)):
|
||
rawPosition = response[i]
|
||
entryPriceString = self.safe_string(rawPosition, 'entryPrice')
|
||
if Precise.string_gt(entryPriceString, '0'):
|
||
result.append(self.parse_position_risk(response[i]))
|
||
symbols = self.market_symbols(symbols)
|
||
return self.filter_by_array_positions(result, 'symbol', symbols, False)
|
||
|
||
def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetch the history of funding payments paid and received on self account
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Get-Income-History
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Get-Income-History
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Income-History
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Income-History
|
||
|
||
:param str symbol: unified market symbol
|
||
:param int [since]: the earliest time in ms to fetch funding history for
|
||
:param int [limit]: the maximum number of funding history structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: timestamp in ms of the latest funding history entry
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch the funding history for a portfolio margin account
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a `funding history structure <https://docs.ccxt.com/#/?id=funding-history-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
request: dict = {
|
||
'incomeType': 'FUNDING_FEE', # "TRANSFER","WELCOME_BONUS", "REALIZED_PNL","FUNDING_FEE", "COMMISSION" and "INSURANCE_CLEAR"
|
||
}
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
if not market['swap']:
|
||
raise NotSupported(self.id + ' fetchFundingHistory() supports swap contracts only')
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchFundingHistory', market, params, 'linear')
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchFundingHistory', 'papi', 'portfolioMargin', False)
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
defaultType = self.safe_string_2(self.options, 'fetchFundingHistory', 'defaultType', 'future')
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
params = self.omit(params, 'type')
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmIncome(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetIncome(self.extend(request, params))
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmIncome(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetIncome(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchFundingHistory() supports linear and inverse contracts only')
|
||
return self.parse_incomes(response, market, since, limit)
|
||
|
||
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
||
"""
|
||
set the level of leverage for a market
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Change-Initial-Leverage
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Change-Initial-Leverage
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Change-UM-Initial-Leverage
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Change-CM-Initial-Leverage
|
||
|
||
: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 boolean [params.portfolioMargin]: set to True if you would like to set the leverage for a trading pair in a portfolio margin account
|
||
:returns dict: response from the exchange
|
||
"""
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
|
||
# WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
|
||
# AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
|
||
if (leverage < 1) or (leverage > 125):
|
||
raise BadRequest(self.id + ' leverage should be between 1 and 125')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'leverage': leverage,
|
||
}
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'setLeverage', 'papi', 'portfolioMargin', False)
|
||
response = None
|
||
if market['linear']:
|
||
if isPortfolioMargin:
|
||
response = self.papiPostUmLeverage(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivatePostLeverage(self.extend(request, params))
|
||
elif market['inverse']:
|
||
if isPortfolioMargin:
|
||
response = self.papiPostCmLeverage(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivatePostLeverage(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' setLeverage() supports linear and inverse contracts only')
|
||
return response
|
||
|
||
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
|
||
"""
|
||
set margin mode to 'cross' or 'isolated'
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Change-Margin-Type
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Change-Margin-Type
|
||
|
||
: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')
|
||
#
|
||
# {"code": -4048 , "msg": "Margin type cannot be changed if there exists position."}
|
||
#
|
||
# or
|
||
#
|
||
# {"code": 200, "msg": "success"}
|
||
#
|
||
marginMode = marginMode.upper()
|
||
if marginMode == 'CROSS':
|
||
marginMode = 'CROSSED'
|
||
if (marginMode != 'ISOLATED') and (marginMode != 'CROSSED'):
|
||
raise BadRequest(self.id + ' marginMode must be either isolated or cross')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
'marginType': marginMode,
|
||
}
|
||
response = None
|
||
try:
|
||
if market['linear']:
|
||
response = self.fapiPrivatePostMarginType(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPrivatePostMarginType(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' setMarginMode() supports linear and inverse contracts only')
|
||
except Exception as e:
|
||
# not an error
|
||
# https://github.com/ccxt/ccxt/issues/11268
|
||
# https://github.com/ccxt/ccxt/pull/11624
|
||
# POST https://fapi.binance.com/fapi/v1/marginType 400 Bad Request
|
||
# binanceusdm
|
||
if isinstance(e, MarginModeAlreadySet):
|
||
throwMarginModeAlreadySet = self.safe_bool(self.options, 'throwMarginModeAlreadySet', False)
|
||
if throwMarginModeAlreadySet:
|
||
raise e
|
||
else:
|
||
response = {'code': -4046, 'msg': 'No need to change margin type.'}
|
||
else:
|
||
raise e
|
||
return response
|
||
|
||
def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
|
||
"""
|
||
set hedged to True or False for a market
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Change-Position-Mode
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Change-Position-Mode
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Current-Position-Mode
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Current-Position-Mode
|
||
|
||
:param bool hedged: set to True to use dualSidePosition
|
||
:param str symbol: not used by binance setPositionMode()
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to set the position mode for a portfolio margin account
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: response from the exchange
|
||
"""
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('setPositionMode', market, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('setPositionMode', market, params)
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'setPositionMode', 'papi', 'portfolioMargin', False)
|
||
dualSidePosition = None
|
||
if hedged:
|
||
dualSidePosition = 'true'
|
||
else:
|
||
dualSidePosition = 'false'
|
||
request: dict = {
|
||
'dualSidePosition': dualSidePosition,
|
||
}
|
||
response = None
|
||
if self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiPostCmPositionSideDual(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivatePostPositionSideDual(self.extend(request, params))
|
||
elif self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiPostUmPositionSideDual(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivatePostPositionSideDual(self.extend(request, params))
|
||
else:
|
||
raise BadRequest(self.id + ' setPositionMode() supports linear and inverse contracts only')
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "success"
|
||
# }
|
||
#
|
||
return response
|
||
|
||
def fetch_leverages(self, symbols: Strings = None, params={}) -> Leverages:
|
||
"""
|
||
fetch the set leverage for all markets
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V2
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Account-Information
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Account-Detail
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Account-Detail
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Symbol-Config
|
||
|
||
:param str[] [symbols]: a list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a list of `leverage structures <https://docs.ccxt.com/#/?id=leverage-structure>`
|
||
"""
|
||
self.load_markets()
|
||
self.load_leverage_brackets(False, params)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchLeverages', None, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchLeverages', None, params, 'linear')
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchLeverages', 'papi', 'portfolioMargin', False)
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmAccount(params)
|
||
else:
|
||
response = self.fapiPrivateGetSymbolConfig(params)
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmAccount(params)
|
||
else:
|
||
response = self.dapiPrivateGetAccount(params)
|
||
else:
|
||
raise NotSupported(self.id + ' fetchLeverages() supports linear and inverse contracts only')
|
||
leverages = self.safe_list(response, 'positions', [])
|
||
if isinstance(response, list):
|
||
leverages = response
|
||
return self.parse_leverages(leverages, symbols, 'symbol')
|
||
|
||
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
|
||
marketId = self.safe_string(leverage, 'symbol')
|
||
marginModeRaw = self.safe_bool(leverage, 'isolated')
|
||
marginMode = None
|
||
if marginModeRaw is not None:
|
||
marginMode = 'isolated' if marginModeRaw else 'cross'
|
||
marginTypeRaw = self.safe_string_lower(leverage, 'marginType')
|
||
if marginTypeRaw is not None:
|
||
marginMode = 'cross' if (marginTypeRaw == 'crossed') else 'isolated'
|
||
side = self.safe_string_lower(leverage, 'positionSide')
|
||
longLeverage = None
|
||
shortLeverage = None
|
||
leverageValue = self.safe_integer(leverage, 'leverage')
|
||
if (side is None) or (side == 'both'):
|
||
longLeverage = leverageValue
|
||
shortLeverage = leverageValue
|
||
elif side == 'long':
|
||
longLeverage = leverageValue
|
||
elif side == 'short':
|
||
shortLeverage = leverageValue
|
||
return {
|
||
'info': leverage,
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'marginMode': marginMode,
|
||
'longLeverage': longLeverage,
|
||
'shortLeverage': shortLeverage,
|
||
}
|
||
|
||
def fetch_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical settlement records
|
||
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Historical-Exercise-Records
|
||
|
||
:param str symbol: unified market symbol of the settlement history
|
||
:param int [since]: timestamp in ms
|
||
:param int [limit]: number of records, default 100, max 100
|
||
:param dict [params]: exchange specific params
|
||
:returns dict[]: a list of `settlement history objects <https://docs.ccxt.com/#/?id=settlement-history-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = None if (symbol is None) else self.market(symbol)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchSettlementHistory', market, params)
|
||
if type != 'option':
|
||
raise NotSupported(self.id + ' fetchSettlementHistory() supports option markets only')
|
||
request: dict = {}
|
||
if symbol is not None:
|
||
symbol = market['symbol']
|
||
request['underlying'] = market['baseId'] + market['quoteId']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.eapiPublicGetExerciseHistory(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ETH-230223-1900-P",
|
||
# "strikePrice": "1900",
|
||
# "realStrikePrice": "1665.5897334",
|
||
# "expiryDate": 1677139200000,
|
||
# "strikeResult": "REALISTIC_VALUE_STRICKEN"
|
||
# }
|
||
# ]
|
||
#
|
||
settlements = self.parse_settlements(response, market)
|
||
sorted = self.sort_by(settlements, 'timestamp')
|
||
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
|
||
|
||
def fetch_my_settlement_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches historical settlement records of the user
|
||
|
||
https://developers.binance.com/docs/derivatives/option/trade/User-Exercise-Record
|
||
|
||
:param str symbol: unified market symbol of the settlement history
|
||
:param int [since]: timestamp in ms
|
||
:param int [limit]: number of records
|
||
:param dict [params]: exchange specific params
|
||
:returns dict[]: a list of [settlement history objects]
|
||
"""
|
||
self.load_markets()
|
||
market = None if (symbol is None) else self.market(symbol)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchMySettlementHistory', market, params)
|
||
if type != 'option':
|
||
raise NotSupported(self.id + ' fetchMySettlementHistory() supports option markets only')
|
||
request: dict = {}
|
||
if symbol is not None:
|
||
request['symbol'] = market['id']
|
||
symbol = market['symbol']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.eapiPrivateGetExerciseRecord(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "id": "1125899906842897036",
|
||
# "currency": "USDT",
|
||
# "symbol": "BTC-230728-30000-C",
|
||
# "exercisePrice": "30000.00000000",
|
||
# "markPrice": "29160.71284993",
|
||
# "quantity": "1.00000000",
|
||
# "amount": "0.00000000",
|
||
# "fee": "0.00000000",
|
||
# "createDate": 1690531200000,
|
||
# "priceScale": 0,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "positionSide": "LONG",
|
||
# "quoteAsset": "USDT"
|
||
# }
|
||
# ]
|
||
#
|
||
settlements = self.parse_settlements(response, market)
|
||
sorted = self.sort_by(settlements, 'timestamp')
|
||
return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)
|
||
|
||
def parse_settlement(self, settlement, market):
|
||
#
|
||
# fetchSettlementHistory
|
||
#
|
||
# {
|
||
# "symbol": "ETH-230223-1900-P",
|
||
# "strikePrice": "1900",
|
||
# "realStrikePrice": "1665.5897334",
|
||
# "expiryDate": 1677139200000,
|
||
# "strikeResult": "REALISTIC_VALUE_STRICKEN"
|
||
# }
|
||
#
|
||
# fetchMySettlementHistory
|
||
#
|
||
# {
|
||
# "id": "1125899906842897036",
|
||
# "currency": "USDT",
|
||
# "symbol": "BTC-230728-30000-C",
|
||
# "exercisePrice": "30000.00000000",
|
||
# "markPrice": "29160.71284993",
|
||
# "quantity": "1.00000000",
|
||
# "amount": "0.00000000",
|
||
# "fee": "0.00000000",
|
||
# "createDate": 1690531200000,
|
||
# "priceScale": 0,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "positionSide": "LONG",
|
||
# "quoteAsset": "USDT"
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer_2(settlement, 'expiryDate', 'createDate')
|
||
marketId = self.safe_string(settlement, 'symbol')
|
||
return {
|
||
'info': settlement,
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'price': self.safe_number_2(settlement, 'realStrikePrice', 'exercisePrice'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
|
||
def parse_settlements(self, settlements, market):
|
||
#
|
||
# fetchSettlementHistory
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ETH-230223-1900-P",
|
||
# "strikePrice": "1900",
|
||
# "realStrikePrice": "1665.5897334",
|
||
# "expiryDate": 1677139200000,
|
||
# "strikeResult": "EXTRINSIC_VALUE_EXPIRED"
|
||
# }
|
||
# ]
|
||
#
|
||
# fetchMySettlementHistory
|
||
#
|
||
# [
|
||
# {
|
||
# "id": "1125899906842897036",
|
||
# "currency": "USDT",
|
||
# "symbol": "BTC-230728-30000-C",
|
||
# "exercisePrice": "30000.00000000",
|
||
# "markPrice": "29160.71284993",
|
||
# "quantity": "1.00000000",
|
||
# "amount": "0.00000000",
|
||
# "fee": "0.00000000",
|
||
# "createDate": 1690531200000,
|
||
# "priceScale": 0,
|
||
# "quantityScale": 2,
|
||
# "optionSide": "CALL",
|
||
# "positionSide": "LONG",
|
||
# "quoteAsset": "USDT"
|
||
# }
|
||
# ]
|
||
#
|
||
result = []
|
||
for i in range(0, len(settlements)):
|
||
result.append(self.parse_settlement(settlements[i], market))
|
||
return result
|
||
|
||
def fetch_ledger_entry(self, id: str, code: Str = None, params={}) -> LedgerEntry:
|
||
"""
|
||
fetch the history of changes, actions done by the user or operations that altered the balance of the user
|
||
|
||
https://developers.binance.com/docs/derivatives/option/account/Account-Funding-Flow
|
||
|
||
:param str id: the identification number of the ledger entry
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
||
"""
|
||
self.load_markets()
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchLedgerEntry', None, params)
|
||
if type != 'option':
|
||
raise BadRequest(self.id + ' fetchLedgerEntry() can only be used for type option')
|
||
self.check_required_argument('fetchLedgerEntry', code, 'code')
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'recordId': id,
|
||
'currency': currency['id'],
|
||
}
|
||
response = self.eapiPrivateGetBill(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "id": "1125899906845701870",
|
||
# "asset": "USDT",
|
||
# "amount": "-0.16518203",
|
||
# "type": "FEE",
|
||
# "createDate": 1676621042489
|
||
# }
|
||
# ]
|
||
#
|
||
first = self.safe_dict(response, 0, response)
|
||
return self.parse_ledger_entry(first, currency)
|
||
|
||
def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
|
||
"""
|
||
fetch the history of changes, actions done by the user or operations that altered the balance of the user
|
||
|
||
https://developers.binance.com/docs/derivatives/option/account/Account-Funding-Flow
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Get-Income-History
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Get-Income-History
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-UM-Income-History
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-CM-Income-History
|
||
|
||
:param str [code]: unified currency code
|
||
:param int [since]: timestamp in ms of the earliest ledger entry
|
||
:param int [limit]: max number of ledger entries to return
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: timestamp in ms of the latest ledger entry
|
||
: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)
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch the ledger for a portfolio margin account
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_dynamic('fetchLedger', code, since, limit, params, None, False)
|
||
type = None
|
||
subType = None
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request: dict = {}
|
||
type, params = self.handle_market_type_and_params('fetchLedger', None, params)
|
||
subType, params = self.handle_sub_type_and_params('fetchLedger', None, params)
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
until = self.safe_integer(params, 'until')
|
||
if until is not None:
|
||
params = self.omit(params, 'until')
|
||
request['endTime'] = until
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchLedger', 'papi', 'portfolioMargin', False)
|
||
response = None
|
||
if type == 'option':
|
||
self.check_required_argument('fetchLedger', code, 'code')
|
||
request['currency'] = currency['id']
|
||
response = self.eapiPrivateGetBill(self.extend(request, params))
|
||
elif self.is_linear(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmIncome(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetIncome(self.extend(request, params))
|
||
elif self.is_inverse(type, subType):
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmIncome(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetIncome(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchLedger() supports contract wallets only')
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# [
|
||
# {
|
||
# "id": "1125899906845701870",
|
||
# "asset": "USDT",
|
||
# "amount": "-0.16518203",
|
||
# "type": "FEE",
|
||
# "createDate": 1676621042489
|
||
# }
|
||
# ]
|
||
#
|
||
# futures(fapi, dapi, papi)
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "",
|
||
# "incomeType": "TRANSFER",
|
||
# "income": "10.00000000",
|
||
# "asset": "USDT",
|
||
# "time": 1677645250000,
|
||
# "info": "TRANSFER",
|
||
# "tranId": 131001573082,
|
||
# "tradeId": ""
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_ledger(response, currency, since, limit)
|
||
|
||
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# {
|
||
# "id": "1125899906845701870",
|
||
# "asset": "USDT",
|
||
# "amount": "-0.16518203",
|
||
# "type": "FEE",
|
||
# "createDate": 167662104241
|
||
# }
|
||
#
|
||
# futures(fapi, dapi, papi)
|
||
#
|
||
# {
|
||
# "symbol": "",
|
||
# "incomeType": "TRANSFER",
|
||
# "income": "10.00000000",
|
||
# "asset": "USDT",
|
||
# "time": 1677645250000,
|
||
# "info": "TRANSFER",
|
||
# "tranId": 131001573082,
|
||
# "tradeId": ""
|
||
# }
|
||
#
|
||
amount = self.safe_string_2(item, 'amount', 'income')
|
||
direction = None
|
||
if Precise.string_le(amount, '0'):
|
||
direction = 'out'
|
||
amount = Precise.string_mul('-1', amount)
|
||
else:
|
||
direction = 'in'
|
||
currencyId = self.safe_string(item, 'asset')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
currency = self.safe_currency(currencyId, currency)
|
||
timestamp = self.safe_integer_2(item, 'createDate', 'time')
|
||
type = self.safe_string_2(item, 'type', 'incomeType')
|
||
return self.safe_ledger_entry({
|
||
'info': item,
|
||
'id': self.safe_string_2(item, 'id', 'tranId'),
|
||
'direction': direction,
|
||
'account': None,
|
||
'referenceAccount': None,
|
||
'referenceId': self.safe_string(item, 'tradeId'),
|
||
'type': self.parse_ledger_entry_type(type),
|
||
'currency': code,
|
||
'amount': self.parse_number(amount),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'before': None,
|
||
'after': None,
|
||
'status': None,
|
||
'fee': None,
|
||
}, currency)
|
||
|
||
def parse_ledger_entry_type(self, type):
|
||
ledgerType: dict = {
|
||
'FEE': 'fee',
|
||
'FUNDING_FEE': 'fee',
|
||
'OPTIONS_PREMIUM_FEE': 'fee',
|
||
'POSITION_LIMIT_INCREASE_FEE': 'fee',
|
||
'CONTRACT': 'trade',
|
||
'REALIZED_PNL': 'trade',
|
||
'TRANSFER': 'transfer',
|
||
'CROSS_COLLATERAL_TRANSFER': 'transfer',
|
||
'INTERNAL_TRANSFER': 'transfer',
|
||
'COIN_SWAP_DEPOSIT': 'deposit',
|
||
'COIN_SWAP_WITHDRAW': 'withdrawal',
|
||
'OPTIONS_SETTLE_PROFIT': 'settlement',
|
||
'DELIVERED_SETTELMENT': 'settlement',
|
||
'WELCOME_BONUS': 'cashback',
|
||
'CONTEST_REWARD': 'cashback',
|
||
'COMMISSION_REBATE': 'rebate',
|
||
'API_REBATE': 'rebate',
|
||
'REFERRAL_KICKBACK': 'referral',
|
||
'COMMISSION': 'commission',
|
||
}
|
||
return self.safe_string(ledgerType, type, type)
|
||
|
||
def get_network_code_by_network_url(self, currencyCode: str, depositUrl: Str = None) -> Str:
|
||
# depositUrl is like : https://bscscan.com/address/0xEF238AB229342849..
|
||
if depositUrl is None:
|
||
return None
|
||
networkCode = None
|
||
currency = self.currency(currencyCode)
|
||
networks = self.safe_dict(currency, 'networks', {})
|
||
networkCodes = list(networks.keys())
|
||
for i in range(0, len(networkCodes)):
|
||
currentNetworkCode = networkCodes[i]
|
||
info = self.safe_dict(networks[currentNetworkCode], 'info', {})
|
||
siteUrl = self.safe_string(info, 'contractAddressUrl')
|
||
# check if url matches the field's value
|
||
if siteUrl is not None and depositUrl.startswith(self.get_base_domain_from_url(siteUrl)):
|
||
networkCode = currentNetworkCode
|
||
return networkCode
|
||
|
||
def get_base_domain_from_url(self, url: Str) -> Str:
|
||
if url is None:
|
||
return None
|
||
urlParts = url.split('/')
|
||
scheme = self.safe_string(urlParts, 0)
|
||
if scheme is None:
|
||
return None
|
||
domain = self.safe_string(urlParts, 2)
|
||
if domain is None:
|
||
return None
|
||
return scheme + '//' + domain + '/'
|
||
|
||
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
||
urls = self.urls
|
||
if not (api in urls['api']):
|
||
raise NotSupported(self.id + ' does not have a testnet/sandbox URL for ' + api + ' endpoints')
|
||
url = self.urls['api'][api]
|
||
url += '/' + path
|
||
if path == 'historicalTrades':
|
||
if self.apiKey:
|
||
headers = {
|
||
'X-MBX-APIKEY': self.apiKey,
|
||
}
|
||
else:
|
||
raise AuthenticationError(self.id + ' historicalTrades endpoint requires `apiKey` credential')
|
||
userDataStream = (path == 'userDataStream') or (path == 'listenKey')
|
||
if userDataStream:
|
||
if self.apiKey:
|
||
# v1 special case for userDataStream
|
||
headers = {
|
||
'X-MBX-APIKEY': self.apiKey,
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
}
|
||
if method != 'GET':
|
||
body = self.urlencode(params)
|
||
else:
|
||
raise AuthenticationError(self.id + ' userDataStream endpoint requires `apiKey` credential')
|
||
elif (api == 'private') or (api == 'eapiPrivate') or (api == 'sapi' and path != 'system/status') or (api == 'sapiV2') or (api == 'sapiV3') or (api == 'sapiV4') or (api == 'dapiPrivate') or (api == 'dapiPrivateV2') or (api == 'fapiPrivate') or (api == 'fapiPrivateV2') or (api == 'fapiPrivateV3') or (api == 'papiV2' or api == 'papi' and path != 'ping'):
|
||
self.check_required_credentials()
|
||
if (url.find('testnet.binancefuture.com') > -1) and self.isSandboxModeEnabled and (not self.safe_bool(self.options, 'disableFuturesSandboxWarning')):
|
||
raise NotSupported(self.id + ' testnet/sandbox mode is not supported for futures anymore, please check the deprecation announcement https://t.me/ccxt_announcements/92 and consider using the demo trading instead.')
|
||
if method == 'POST' and ((path == 'order') or (path == 'sor/order')):
|
||
# inject in implicit API calls
|
||
newClientOrderId = self.safe_string(params, 'newClientOrderId')
|
||
if newClientOrderId is None:
|
||
isSpotOrMargin = (api.find('sapi') > -1 or api == 'private')
|
||
marketType = 'spot' if isSpotOrMargin else 'future'
|
||
defaultId = 'x-xcKtGhcu' if (not isSpotOrMargin) else 'x-TKT5PX2F'
|
||
broker = self.safe_dict(self.options, 'broker', {})
|
||
brokerId = self.safe_string(broker, marketType, defaultId)
|
||
params['newClientOrderId'] = brokerId + self.uuid22()
|
||
query = None
|
||
# handle batchOrders
|
||
if (path == 'batchOrders') and ((method == 'POST') or (method == 'PUT')):
|
||
batchOrders = self.safe_list(params, 'batchOrders')
|
||
checkedBatchOrders = batchOrders
|
||
if method == 'POST' and api == 'fapiPrivate':
|
||
# check broker id if batchOrders are called with fapiPrivatePostBatchOrders
|
||
checkedBatchOrders = []
|
||
for i in range(0, len(batchOrders)):
|
||
batchOrder = batchOrders[i]
|
||
newClientOrderId = self.safe_string(batchOrder, 'newClientOrderId')
|
||
if newClientOrderId is None:
|
||
defaultId = 'x-xcKtGhcu' # batchOrders can not be spot or margin
|
||
broker = self.safe_dict(self.options, 'broker', {})
|
||
brokerId = self.safe_string(broker, 'future', defaultId)
|
||
newClientOrderId = brokerId + self.uuid22()
|
||
batchOrder['newClientOrderId'] = newClientOrderId
|
||
checkedBatchOrders.append(batchOrder)
|
||
queryBatch = (self.json(checkedBatchOrders))
|
||
params['batchOrders'] = queryBatch
|
||
defaultRecvWindow = self.safe_integer(self.options, 'recvWindow')
|
||
extendedParams = self.extend({
|
||
'timestamp': self.nonce(),
|
||
}, params)
|
||
if defaultRecvWindow is not None:
|
||
extendedParams['recvWindow'] = defaultRecvWindow
|
||
recvWindow = self.safe_integer(params, 'recvWindow')
|
||
if recvWindow is not None:
|
||
extendedParams['recvWindow'] = recvWindow
|
||
if (api == 'sapi') and (path == 'asset/dust'):
|
||
query = self.urlencode_with_array_repeat(extendedParams)
|
||
elif (path == 'batchOrders') or (path.find('sub-account') >= 0) or (path == 'capital/withdraw/apply') or (path.find('staking') >= 0) or (path.find('simple-earn') >= 0):
|
||
if (method == 'DELETE') and (path == 'batchOrders'):
|
||
orderidlist = self.safe_list(extendedParams, 'orderidlist', [])
|
||
origclientorderidlist = self.safe_list_2(extendedParams, 'origclientorderidlist', 'origClientOrderIdList', [])
|
||
extendedParams = self.omit(extendedParams, ['orderidlist', 'origclientorderidlist', 'origClientOrderIdList'])
|
||
if 'symbol' in extendedParams:
|
||
extendedParams['symbol'] = self.encode_uri_component(extendedParams['symbol'])
|
||
query = self.rawencode(extendedParams)
|
||
orderidlistLength = len(orderidlist)
|
||
origclientorderidlistLength = len(origclientorderidlist)
|
||
if orderidlistLength > 0:
|
||
query = query + '&' + 'orderidlist=%5B' + '%2C'.join(orderidlist) + '%5D'
|
||
if origclientorderidlistLength > 0:
|
||
# wrap clientOrderids around ""
|
||
newClientOrderIds = []
|
||
for i in range(0, origclientorderidlistLength):
|
||
newClientOrderIds.append('%22' + origclientorderidlist[i] + '%22')
|
||
query = query + '&' + 'origclientorderidlist=%5B' + '%2C'.join(newClientOrderIds) + '%5D'
|
||
else:
|
||
query = self.rawencode(extendedParams)
|
||
else:
|
||
query = self.urlencode(extendedParams)
|
||
signature = None
|
||
if self.secret.find('PRIVATE KEY') > -1:
|
||
if len(self.secret) > 120:
|
||
signature = self.encode_uri_component(self.rsa(query, self.secret, 'sha256'))
|
||
else:
|
||
signature = self.encode_uri_component(self.eddsa(self.encode(query), self.secret, 'ed25519'))
|
||
else:
|
||
signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256)
|
||
query += '&' + 'signature=' + signature
|
||
headers = {
|
||
'X-MBX-APIKEY': self.apiKey,
|
||
}
|
||
if (method == 'GET') or (method == 'DELETE'):
|
||
url += '?' + query
|
||
else:
|
||
body = query
|
||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||
else:
|
||
if params:
|
||
url += '?' + self.urlencode(params)
|
||
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
||
|
||
def get_exceptions_by_url(self, url: str, exactOrBroad: str):
|
||
marketType = None
|
||
hostname = self.hostname if (self.hostname is not None) else 'binance.com'
|
||
if url.startswith('https://api.' + hostname + '/') or url.startswith('https://testnet.binance.vision'):
|
||
marketType = 'spot'
|
||
elif url.startswith('https://dapi.' + hostname + '/') or url.startswith('https://testnet.binancefuture.com/dapi'):
|
||
marketType = 'inverse'
|
||
elif url.startswith('https://fapi.' + hostname + '/') or url.startswith('https://testnet.binancefuture.com/fapi'):
|
||
marketType = 'linear'
|
||
elif url.startswith('https://eapi.' + hostname + '/'):
|
||
marketType = 'option'
|
||
elif url.startswith('https://papi.' + hostname + '/'):
|
||
marketType = 'portfolioMargin'
|
||
if marketType is not None:
|
||
exceptionsForMarketType = self.safe_dict(self.exceptions, marketType, {})
|
||
return self.safe_dict(exceptionsForMarketType, exactOrBroad, {})
|
||
return {}
|
||
|
||
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
||
if (code == 418) or (code == 429):
|
||
raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
|
||
# error response in a form: {"code": -1013, "msg": "Invalid quantity."}
|
||
# following block cointains legacy checks against message patterns in "msg" property
|
||
# will switch "code" checks eventually, when we know all of them
|
||
if code >= 400:
|
||
if body.find('Price * QTY is zero or less') >= 0:
|
||
raise InvalidOrder(self.id + ' order cost = amount * price is zero or less ' + body)
|
||
if body.find('LOT_SIZE') >= 0:
|
||
raise InvalidOrder(self.id + ' order amount should be evenly divisible by lot size ' + body)
|
||
if body.find('PRICE_FILTER') >= 0:
|
||
raise InvalidOrder(self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid value in general, use self.price_to_precision(symbol, amount) ' + body)
|
||
if response is None:
|
||
return None # fallback to default error handler
|
||
# response in format {'msg': 'The coin does not exist.', 'success': True/false}
|
||
success = self.safe_bool(response, 'success', True)
|
||
if not success:
|
||
messageNew = self.safe_string(response, 'msg')
|
||
parsedMessage = None
|
||
if messageNew is not None:
|
||
try:
|
||
parsedMessage = json.loads(messageNew)
|
||
except Exception as e:
|
||
# do nothing
|
||
parsedMessage = None
|
||
if parsedMessage is not None:
|
||
response = parsedMessage
|
||
message = self.safe_string(response, 'msg')
|
||
if message is not None:
|
||
self.throw_exactly_matched_exception(self.get_exceptions_by_url(url, 'exact'), message, self.id + ' ' + message)
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message)
|
||
self.throw_broadly_matched_exception(self.get_exceptions_by_url(url, 'broad'), message, self.id + ' ' + message)
|
||
self.throw_broadly_matched_exception(self.exceptions['broad'], message, self.id + ' ' + message)
|
||
# checks against error codes
|
||
error = self.safe_string(response, 'code')
|
||
if error is not None:
|
||
# https://github.com/ccxt/ccxt/issues/6501
|
||
# https://github.com/ccxt/ccxt/issues/7742
|
||
if (error == '200') or Precise.string_equals(error, '0'):
|
||
return None
|
||
# a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
|
||
# despite that their message is very confusing, it is raised by Binance
|
||
# on a temporary ban, the API key is valid, but disabled for a while
|
||
if (error == '-2015') and self.options['hasAlreadyAuthenticatedSuccessfully']:
|
||
raise DDoSProtection(self.id + ' ' + body)
|
||
feedback = self.id + ' ' + body
|
||
if message == 'No need to change margin type.':
|
||
# not an error
|
||
# https://github.com/ccxt/ccxt/issues/11268
|
||
# https://github.com/ccxt/ccxt/pull/11624
|
||
# POST https://fapi.binance.com/fapi/v1/marginType 400 Bad Request
|
||
# binanceusdm {"code":-4046,"msg":"No need to change margin type."}
|
||
raise MarginModeAlreadySet(feedback)
|
||
self.throw_exactly_matched_exception(self.get_exceptions_by_url(url, 'exact'), error, feedback)
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
|
||
raise ExchangeError(feedback)
|
||
if not success:
|
||
raise ExchangeError(self.id + ' ' + body)
|
||
if isinstance(response, list):
|
||
# cancelOrders returns an array like self: [{"code":-2011,"msg":"Unknown order sent."}]
|
||
arrayLength = len(response)
|
||
if arrayLength == 1: # when there's a single error we can throw, otherwise we have a partial success
|
||
element = response[0]
|
||
errorCode = self.safe_string(element, 'code')
|
||
if errorCode is not None:
|
||
self.throw_exactly_matched_exception(self.get_exceptions_by_url(url, 'exact'), errorCode, self.id + ' ' + body)
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, self.id + ' ' + body)
|
||
return None
|
||
|
||
def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
|
||
if ('noCoin' in config) and not ('coin' in params):
|
||
return config['noCoin']
|
||
elif ('noSymbol' in config) and not ('symbol' in params):
|
||
return config['noSymbol']
|
||
elif ('noPoolId' in config) and not ('poolId' in params):
|
||
return config['noPoolId']
|
||
elif ('byLimit' in config) and ('limit' in params):
|
||
limit = params['limit']
|
||
byLimit = config['byLimit']
|
||
for i in range(0, len(byLimit)):
|
||
entry = byLimit[i]
|
||
if limit <= entry[0]:
|
||
return entry[1]
|
||
return self.safe_value(config, 'cost', 1)
|
||
|
||
def request(self, path, api='public', method='GET', params={}, headers=None, body=None, config={}):
|
||
response = self.fetch2(path, api, method, params, headers, body, config)
|
||
# a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
|
||
if api == 'private':
|
||
self.options['hasAlreadyAuthenticatedSuccessfully'] = True
|
||
return response
|
||
|
||
def modify_margin_helper(self, symbol: str, amount, addOrReduce, params={}):
|
||
# used to modify isolated positions
|
||
defaultType = self.safe_string(self.options, 'defaultType', 'future')
|
||
if defaultType == 'spot':
|
||
defaultType = 'future'
|
||
type = self.safe_string(params, 'type', defaultType)
|
||
if (type == 'margin') or (type == 'spot'):
|
||
raise NotSupported(self.id + ' add / reduce margin only supported with type future or delivery')
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
amount = self.amount_to_precision(symbol, amount)
|
||
request: dict = {
|
||
'type': addOrReduce,
|
||
'symbol': market['id'],
|
||
'amount': amount,
|
||
}
|
||
response = None
|
||
code = None
|
||
if market['linear']:
|
||
code = market['quote']
|
||
response = self.fapiPrivatePostPositionMargin(self.extend(request, params))
|
||
else:
|
||
code = market['base']
|
||
response = self.dapiPrivatePostPositionMargin(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "Successfully modify position margin.",
|
||
# "amount": 0.001,
|
||
# "type": 1
|
||
# }
|
||
#
|
||
return self.extend(self.parse_margin_modification(response, market), {
|
||
'code': code,
|
||
})
|
||
|
||
def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
|
||
#
|
||
# add/reduce margin
|
||
#
|
||
# {
|
||
# "code": 200,
|
||
# "msg": "Successfully modify position margin.",
|
||
# "amount": 0.001,
|
||
# "type": 1
|
||
# }
|
||
#
|
||
# fetchMarginAdjustmentHistory
|
||
#
|
||
# {
|
||
# symbol: "XRPUSDT",
|
||
# type: "1",
|
||
# deltaType: "TRADE",
|
||
# amount: "2.57148240",
|
||
# asset: "USDT",
|
||
# time: "1711046271555",
|
||
# positionSide: "BOTH",
|
||
# clientTranId: ""
|
||
# }
|
||
#
|
||
rawType = self.safe_integer(data, 'type')
|
||
errorCode = self.safe_string(data, 'code')
|
||
marketId = self.safe_string(data, 'symbol')
|
||
timestamp = self.safe_integer(data, 'time')
|
||
market = self.safe_market(marketId, market, None, 'swap')
|
||
noErrorCode = errorCode is None
|
||
success = errorCode == '200'
|
||
return {
|
||
'info': data,
|
||
'symbol': market['symbol'],
|
||
'type': 'add' if (rawType == 1) else 'reduce',
|
||
'marginMode': 'isolated',
|
||
'amount': self.safe_number(data, 'amount'),
|
||
'code': self.safe_string(data, 'asset'),
|
||
'total': None,
|
||
'status': 'ok' if (success or noErrorCode) else 'failed',
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
|
||
def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
||
"""
|
||
remove margin from a position
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Modify-Isolated-Position-Margin
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Modify-Isolated-Position-Margin
|
||
|
||
:param str symbol: unified market symbol
|
||
:param float amount: the amount of margin to remove
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=reduce-margin-structure>`
|
||
"""
|
||
return self.modify_margin_helper(symbol, amount, 2, params)
|
||
|
||
def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
|
||
"""
|
||
add margin
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Modify-Isolated-Position-Margin
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Modify-Isolated-Position-Margin
|
||
|
||
:param str symbol: unified market symbol
|
||
:param float amount: amount of margin to add
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
|
||
"""
|
||
return self.modify_margin_helper(symbol, amount, 1, params)
|
||
|
||
def fetch_cross_borrow_rate(self, code: str, params={}) -> CrossBorrowRate:
|
||
"""
|
||
fetch the rate of interest to borrow a currency for margin trading
|
||
|
||
https://developers.binance.com/docs/margin_trading/borrow-and-repay/Query-Margin-Interest-Rate-History
|
||
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `borrow rate structure <https://docs.ccxt.com/#/?id=borrow-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
# 'vipLevel': self.safe_integer(params, 'vipLevel'),
|
||
}
|
||
response = self.sapiGetMarginInterestRateHistory(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "asset": "USDT",
|
||
# "timestamp": 1638230400000,
|
||
# "dailyInterestRate": "0.0006",
|
||
# "vipLevel": 0
|
||
# },
|
||
# ]
|
||
#
|
||
rate = self.safe_dict(response, 0)
|
||
return self.parse_borrow_rate(rate)
|
||
|
||
def fetch_isolated_borrow_rate(self, symbol: str, params={}) -> IsolatedBorrowRate:
|
||
"""
|
||
fetch the rate of interest to borrow a currency for margin trading
|
||
|
||
https://developers.binance.com/docs/margin_trading/account/Query-Isolated-Margin-Fee-Data
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
|
||
EXCHANGE SPECIFIC PARAMETERS
|
||
:param dict [params.vipLevel]: user's current specific margin data will be returned if viplevel is omitted
|
||
:returns dict: an `isolated borrow rate structure <https://docs.ccxt.com/#/?id=isolated-borrow-rate-structure>`
|
||
"""
|
||
request: dict = {
|
||
'symbol': symbol,
|
||
}
|
||
borrowRates = self.fetch_isolated_borrow_rates(self.extend(request, params))
|
||
return self.safe_dict(borrowRates, symbol)
|
||
|
||
def fetch_isolated_borrow_rates(self, params={}) -> IsolatedBorrowRates:
|
||
"""
|
||
fetch the borrow interest rates of all currencies
|
||
|
||
https://developers.binance.com/docs/margin_trading/account/Query-Isolated-Margin-Fee-Data
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param dict [params.symbol]: unified market symbol
|
||
|
||
EXCHANGE SPECIFIC PARAMETERS
|
||
:param dict [params.vipLevel]: user's current specific margin data will be returned if viplevel is omitted
|
||
:returns dict: a `borrow rate structure <https://docs.ccxt.com/#/?id=borrow-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
symbol = self.safe_string(params, 'symbol')
|
||
params = self.omit(params, 'symbol')
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['symbol'] = market['id']
|
||
response = self.sapiGetMarginIsolatedMarginData(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "vipLevel": 0,
|
||
# "symbol": "BTCUSDT",
|
||
# "leverage": "10",
|
||
# "data": [
|
||
# {
|
||
# "coin": "BTC",
|
||
# "dailyInterest": "0.00026125",
|
||
# "borrowLimit": "270"
|
||
# },
|
||
# {
|
||
# "coin": "USDT",
|
||
# "dailyInterest": "0.000475",
|
||
# "borrowLimit": "2100000"
|
||
# }
|
||
# ]
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_isolated_borrow_rates(response)
|
||
|
||
def fetch_borrow_rate_history(self, code: str, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
retrieves a history of a currencies borrow interest rate at specific time slots
|
||
|
||
https://developers.binance.com/docs/margin_trading/borrow-and-repay/Query-Margin-Interest-Rate-History
|
||
|
||
:param str code: unified currency code
|
||
:param int [since]: timestamp for the earliest borrow rate
|
||
:param int [limit]: the maximum number of `borrow rate structures <https://docs.ccxt.com/#/?id=borrow-rate-structure>` to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: an array of `borrow rate structures <https://docs.ccxt.com/#/?id=borrow-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
if limit is None:
|
||
limit = 93
|
||
elif limit > 93:
|
||
# Binance API says the limit is 100, but "Illegal characters found in a parameter." is returned when limit is > 93
|
||
raise BadRequest(self.id + ' fetchBorrowRateHistory() limit parameter cannot exceed 92')
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
'limit': limit,
|
||
}
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
endTime = self.sum(since, limit * 86400000) - 1 # required when startTime is further than 93 days in the past
|
||
now = self.milliseconds()
|
||
request['endTime'] = min(endTime, now) # cannot have an endTime later than current time
|
||
response = self.sapiGetMarginInterestRateHistory(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "asset": "USDT",
|
||
# "timestamp": 1638230400000,
|
||
# "dailyInterestRate": "0.0006",
|
||
# "vipLevel": 0
|
||
# },
|
||
# ]
|
||
#
|
||
return self.parse_borrow_rate_history(response, code, since, limit)
|
||
|
||
def parse_borrow_rate(self, info, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "asset": "USDT",
|
||
# "timestamp": 1638230400000,
|
||
# "dailyInterestRate": "0.0006",
|
||
# "vipLevel": 0
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer(info, 'timestamp')
|
||
currencyId = self.safe_string(info, 'asset')
|
||
return {
|
||
'currency': self.safe_currency_code(currencyId, currency),
|
||
'rate': self.safe_number(info, 'dailyInterestRate'),
|
||
'period': 86400000,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'info': info,
|
||
}
|
||
|
||
def parse_isolated_borrow_rate(self, info: dict, market: Market = None) -> IsolatedBorrowRate:
|
||
#
|
||
# {
|
||
# "vipLevel": 0,
|
||
# "symbol": "BTCUSDT",
|
||
# "leverage": "10",
|
||
# "data": [
|
||
# {
|
||
# "coin": "BTC",
|
||
# "dailyInterest": "0.00026125",
|
||
# "borrowLimit": "270"
|
||
# },
|
||
# {
|
||
# "coin": "USDT",
|
||
# "dailyInterest": "0.000475",
|
||
# "borrowLimit": "2100000"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
marketId = self.safe_string(info, 'symbol')
|
||
market = self.safe_market(marketId, market, None, 'spot')
|
||
data = self.safe_list(info, 'data')
|
||
baseInfo = self.safe_dict(data, 0)
|
||
quoteInfo = self.safe_dict(data, 1)
|
||
return {
|
||
'info': info,
|
||
'symbol': self.safe_string(market, 'symbol'),
|
||
'base': self.safe_string(baseInfo, 'coin'),
|
||
'baseRate': self.safe_number(baseInfo, 'dailyInterest'),
|
||
'quote': self.safe_string(quoteInfo, 'coin'),
|
||
'quoteRate': self.safe_number(quoteInfo, 'dailyInterest'),
|
||
'period': 86400000,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
}
|
||
|
||
def create_gift_code(self, code: str, amount, params={}):
|
||
"""
|
||
create gift code
|
||
|
||
https://developers.binance.com/docs/gift_card/market-data/Create-a-single-token-gift-card
|
||
|
||
:param str code: gift code
|
||
:param float amount: amount of currency for the gift
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: The gift code id, code, currency and amount
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
# ensure you have enough token in your funding account before calling self code
|
||
request: dict = {
|
||
'token': currency['id'],
|
||
'amount': amount,
|
||
}
|
||
response = self.sapiPostGiftcardCreateCode(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "000000",
|
||
# "message": "success",
|
||
# "data": {referenceNo: "0033002404219823", code: "AP6EXTLKNHM6CEX7"},
|
||
# "success": True
|
||
# }
|
||
#
|
||
data = self.safe_dict(response, 'data')
|
||
giftcardCode = self.safe_string(data, 'code')
|
||
id = self.safe_string(data, 'referenceNo')
|
||
return {
|
||
'info': response,
|
||
'id': id,
|
||
'code': giftcardCode,
|
||
'currency': code,
|
||
'amount': amount,
|
||
}
|
||
|
||
def redeem_gift_code(self, giftcardCode, params={}):
|
||
"""
|
||
redeem gift code
|
||
|
||
https://developers.binance.com/docs/gift_card/market-data/Redeem-a-Binance-Gift-Card
|
||
|
||
:param str giftcardCode:
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: response from the exchange
|
||
"""
|
||
request: dict = {
|
||
'code': giftcardCode,
|
||
}
|
||
response = self.sapiPostGiftcardRedeemCode(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "000000",
|
||
# "message": "success",
|
||
# "data": {
|
||
# "referenceNo": "0033002404219823",
|
||
# "identityNo": "10316431732801474560"
|
||
# },
|
||
# "success": True
|
||
# }
|
||
#
|
||
return response
|
||
|
||
def verify_gift_code(self, id: str, params={}):
|
||
"""
|
||
verify gift code
|
||
|
||
https://developers.binance.com/docs/gift_card/market-data/Verify-Binance-Gift-Card-by-Gift-Card-Number
|
||
|
||
:param str id: reference number id
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: response from the exchange
|
||
"""
|
||
request: dict = {
|
||
'referenceNo': id,
|
||
}
|
||
response = self.sapiGetGiftcardVerify(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "code": "000000",
|
||
# "message": "success",
|
||
# "data": {valid: True},
|
||
# "success": True
|
||
# }
|
||
#
|
||
return response
|
||
|
||
def fetch_borrow_interest(self, code: Str = None, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[BorrowInterest]:
|
||
"""
|
||
fetch the interest owed by the user for borrowing currency for margin trading
|
||
|
||
https://developers.binance.com/docs/margin_trading/borrow-and-repay/Get-Interest-History
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/account/Get-Margin-BorrowLoan-Interest-History
|
||
|
||
:param str [code]: unified currency code
|
||
:param str [symbol]: unified market symbol when fetch interest in isolated markets
|
||
:param int [since]: the earliest time in ms to fetch borrrow interest for
|
||
:param int [limit]: the maximum number of structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch the borrow interest in a portfolio margin account
|
||
:returns dict[]: a list of `borrow interest structures <https://docs.ccxt.com/#/?id=borrow-interest-structure>`
|
||
"""
|
||
self.load_markets()
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchBorrowInterest', 'papi', 'portfolioMargin', False)
|
||
request: dict = {}
|
||
market = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['asset'] = currency['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['size'] = limit
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = None
|
||
if isPortfolioMargin:
|
||
response = self.papiGetMarginMarginInterestHistory(self.extend(request, params))
|
||
else:
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['isolatedSymbol'] = market['id']
|
||
response = self.sapiGetMarginInterestHistory(self.extend(request, params))
|
||
#
|
||
# spot margin
|
||
#
|
||
# {
|
||
# "rows":[
|
||
# {
|
||
# "isolatedSymbol": "BNBUSDT", # isolated symbol, will not be returned for crossed margin
|
||
# "asset": "BNB",
|
||
# "interest": "0.02414667",
|
||
# "interestAccuredTime": 1566813600000,
|
||
# "interestRate": "0.01600000",
|
||
# "principal": "36.22000000",
|
||
# "type": "ON_BORROW"
|
||
# }
|
||
# ],
|
||
# "total": 1
|
||
# }
|
||
#
|
||
# spot margin portfolio margin
|
||
#
|
||
# {
|
||
# "total": 49,
|
||
# "rows": [
|
||
# {
|
||
# "txId": 1656187724899910076,
|
||
# "interestAccuredTime": 1707541200000,
|
||
# "asset": "USDT",
|
||
# "rawAsset": "USDT",
|
||
# "principal": "0.00011146",
|
||
# "interest": "0.00000001",
|
||
# "interestRate": "0.00089489",
|
||
# "type": "PERIODIC"
|
||
# },
|
||
# ]
|
||
# }
|
||
#
|
||
rows = self.safe_list(response, 'rows')
|
||
interest = self.parse_borrow_interests(rows, market)
|
||
return self.filter_by_currency_since_limit(interest, code, since, limit)
|
||
|
||
def parse_borrow_interest(self, info: dict, market: Market = None) -> BorrowInterest:
|
||
symbol = self.safe_string(info, 'isolatedSymbol')
|
||
timestamp = self.safe_integer(info, 'interestAccuredTime')
|
||
marginMode = 'cross' if (symbol is None) else 'isolated'
|
||
return {
|
||
'info': info,
|
||
'symbol': symbol,
|
||
'currency': self.safe_currency_code(self.safe_string(info, 'asset')),
|
||
'interest': self.safe_number(info, 'interest'),
|
||
'interestRate': self.safe_number(info, 'interestRate'),
|
||
'amountBorrowed': self.safe_number(info, 'principal'),
|
||
'marginMode': marginMode,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
}
|
||
|
||
def repay_cross_margin(self, code: str, amount, params={}):
|
||
"""
|
||
repay borrowed margin and interest
|
||
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Repay
|
||
https://developers.binance.com/docs/margin_trading/borrow-and-repay/Margin-Account-Borrow-Repay
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Repay-Debt
|
||
|
||
:param str code: unified currency code of the currency to repay
|
||
:param float amount: the amount to repay
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to repay margin in a portfolio margin account
|
||
:param str [params.repayCrossMarginMethod]: *portfolio margin only* 'papiPostRepayLoan'(default), 'papiPostMarginRepayDebt'(alternative)
|
||
:param str [params.specifyRepayAssets]: *portfolio margin papiPostMarginRepayDebt only* specific asset list to repay debt
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
'amount': self.currency_to_precision(code, amount),
|
||
}
|
||
response = None
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'repayCrossMargin', 'papi', 'portfolioMargin', False)
|
||
if isPortfolioMargin:
|
||
method = None
|
||
method, params = self.handle_option_and_params_2(params, 'repayCrossMargin', 'repayCrossMarginMethod', 'method')
|
||
if method == 'papiPostMarginRepayDebt':
|
||
response = self.papiPostMarginRepayDebt(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "asset": "USDC",
|
||
# "amount": 10,
|
||
# "specifyRepayAssets": null,
|
||
# "updateTime": 1727170761267,
|
||
# "success": True
|
||
# }
|
||
#
|
||
else:
|
||
response = self.papiPostRepayLoan(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId": 108988250265,
|
||
# "clientTag":""
|
||
# }
|
||
#
|
||
else:
|
||
request['isIsolated'] = 'FALSE'
|
||
request['type'] = 'REPAY'
|
||
response = self.sapiPostMarginBorrowRepay(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId": 108988250265,
|
||
# "clientTag":""
|
||
# }
|
||
#
|
||
return self.parse_margin_loan(response, currency)
|
||
|
||
def repay_isolated_margin(self, symbol: str, code: str, amount, params={}):
|
||
"""
|
||
repay borrowed margin and interest
|
||
|
||
https://developers.binance.com/docs/margin_trading/borrow-and-repay/Margin-Account-Borrow-Repay
|
||
|
||
:param str symbol: unified market symbol, required for isolated margin
|
||
:param str code: unified currency code of the currency to repay
|
||
:param float amount: the amount to repay
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
'amount': self.currency_to_precision(code, amount),
|
||
'symbol': market['id'],
|
||
'isIsolated': 'TRUE',
|
||
'type': 'REPAY',
|
||
}
|
||
response = self.sapiPostMarginBorrowRepay(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId": 108988250265,
|
||
# "clientTag":""
|
||
# }
|
||
#
|
||
return self.parse_margin_loan(response, currency)
|
||
|
||
def borrow_cross_margin(self, code: str, amount: float, params={}):
|
||
"""
|
||
create a loan to borrow margin
|
||
|
||
https://developers.binance.com/docs/margin_trading/borrow-and-repay/Margin-Account-Borrow-Repay
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Borrow
|
||
|
||
:param str code: unified currency code of the currency to borrow
|
||
:param float amount: the amount to borrow
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to borrow margin in a portfolio margin account
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
'amount': self.currency_to_precision(code, amount),
|
||
}
|
||
response = None
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'borrowCrossMargin', 'papi', 'portfolioMargin', False)
|
||
if isPortfolioMargin:
|
||
response = self.papiPostMarginLoan(self.extend(request, params))
|
||
else:
|
||
request['isIsolated'] = 'FALSE'
|
||
request['type'] = 'BORROW'
|
||
response = self.sapiPostMarginBorrowRepay(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId": 108988250265,
|
||
# "clientTag":""
|
||
# }
|
||
#
|
||
return self.parse_margin_loan(response, currency)
|
||
|
||
def borrow_isolated_margin(self, symbol: str, code: str, amount: float, params={}):
|
||
"""
|
||
create a loan to borrow margin
|
||
|
||
https://developers.binance.com/docs/margin_trading/borrow-and-repay/Margin-Account-Borrow-Repay
|
||
|
||
:param str symbol: unified market symbol, required for isolated margin
|
||
:param str code: unified currency code of the currency to borrow
|
||
:param float amount: the amount to borrow
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `margin loan structure <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'asset': currency['id'],
|
||
'amount': self.currency_to_precision(code, amount),
|
||
'symbol': market['id'],
|
||
'isIsolated': 'TRUE',
|
||
'type': 'BORROW',
|
||
}
|
||
response = self.sapiPostMarginBorrowRepay(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId": 108988250265,
|
||
# "clientTag":""
|
||
# }
|
||
#
|
||
return self.parse_margin_loan(response, currency)
|
||
|
||
def parse_margin_loan(self, info, currency: Currency = None):
|
||
#
|
||
# {
|
||
# "tranId": 108988250265,
|
||
# "clientTag":""
|
||
# }
|
||
#
|
||
# repayCrossMargin alternative endpoint
|
||
#
|
||
# {
|
||
# "asset": "USDC",
|
||
# "amount": 10,
|
||
# "specifyRepayAssets": null,
|
||
# "updateTime": 1727170761267,
|
||
# "success": True
|
||
# }
|
||
#
|
||
currencyId = self.safe_string(info, 'asset')
|
||
timestamp = self.safe_integer(info, 'updateTime')
|
||
return {
|
||
'id': self.safe_integer(info, 'tranId'),
|
||
'currency': self.safe_currency_code(currencyId, currency),
|
||
'amount': self.safe_number(info, 'amount'),
|
||
'symbol': None,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'info': info,
|
||
}
|
||
|
||
def fetch_open_interest_history(self, symbol: str, timeframe='5m', since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
Retrieves the open interest history of a currency
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Open-Interest-Statistics
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Open-Interest-Statistics
|
||
|
||
:param str symbol: Unified CCXT market symbol
|
||
:param str timeframe: "5m","15m","30m","1h","2h","4h","6h","12h", or "1d"
|
||
:param int [since]: the time(ms) of the earliest record to retrieve unix timestamp
|
||
:param int [limit]: default 30, max 500
|
||
:param dict [params]: exchange specific parameters
|
||
:param int [params.until]: the time(ms) of the latest record to retrieve unix timestamp
|
||
: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: an array of `open interest structure <https://docs.ccxt.com/#/?id=open-interest-structure>`
|
||
"""
|
||
if timeframe == '1m':
|
||
raise BadRequest(self.id + ' fetchOpenInterestHistory cannot use the 1m timeframe')
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchOpenInterestHistory', 'paginate', False)
|
||
if paginate:
|
||
return self.fetch_paginated_call_deterministic('fetchOpenInterestHistory', symbol, since, limit, timeframe, params, 500)
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'period': self.safe_string(self.timeframes, timeframe, timeframe),
|
||
}
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
symbolKey = 'symbol' if market['linear'] else 'pair'
|
||
request[symbolKey] = market['id']
|
||
if market['inverse']:
|
||
request['contractType'] = self.safe_string(params, 'contractType', 'CURRENT_QUARTER')
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
until = self.safe_integer(params, 'until') # unified in milliseconds
|
||
endTime = self.safe_integer(params, 'endTime', until) # exchange-specific in milliseconds
|
||
params = self.omit(params, ['endTime', 'until'])
|
||
if endTime:
|
||
request['endTime'] = endTime
|
||
elif since:
|
||
if limit is None:
|
||
limit = 30 # Exchange default
|
||
duration = self.parse_timeframe(timeframe)
|
||
request['endTime'] = self.sum(since, duration * limit * 1000)
|
||
response = None
|
||
if market['inverse']:
|
||
response = self.dapiDataGetOpenInterestHist(self.extend(request, params))
|
||
else:
|
||
response = self.fapiDataGetOpenInterestHist(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol":"BTCUSDT",
|
||
# "sumOpenInterest":"75375.61700000",
|
||
# "sumOpenInterestValue":"3248828883.71251440",
|
||
# "timestamp":1642179900000
|
||
# },
|
||
# ...
|
||
# ]
|
||
#
|
||
return self.parse_open_interests_history(response, market, since, limit)
|
||
|
||
def fetch_open_interest(self, symbol: str, params={}):
|
||
"""
|
||
retrieves the open interest of a contract trading pair
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Open-Interest
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Open-Interest
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Open-Interest
|
||
|
||
: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 = {}
|
||
if market['option']:
|
||
request['underlyingAsset'] = market['baseId']
|
||
if market['expiry'] is None:
|
||
raise NotSupported(self.id + ' fetchOpenInterest does not support ' + symbol)
|
||
request['expiration'] = self.yymmdd(market['expiry'])
|
||
else:
|
||
request['symbol'] = market['id']
|
||
response = None
|
||
if market['option']:
|
||
response = self.eapiPublicGetOpenInterest(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPublicGetOpenInterest(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPublicGetOpenInterest(self.extend(request, params))
|
||
#
|
||
# futures(fapi)
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSDT_230331",
|
||
# "openInterest": "23581.677",
|
||
# "time": 1677356872265
|
||
# }
|
||
#
|
||
# futures(dapi)
|
||
#
|
||
# {
|
||
# "symbol": "ETHUSD_PERP",
|
||
# "pair": "ETHUSD",
|
||
# "openInterest": "26542436",
|
||
# "contractType": "PERPETUAL",
|
||
# "time": 1677360272224
|
||
# }
|
||
#
|
||
# options(eapi)
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "ETH-230225-1625-C",
|
||
# "sumOpenInterest": "460.50",
|
||
# "sumOpenInterestUsd": "734957.4358092150",
|
||
# "timestamp": "1677304860000"
|
||
# }
|
||
# ]
|
||
#
|
||
if market['option']:
|
||
symbol = market['symbol']
|
||
result = self.parse_open_interests_history(response, market)
|
||
for i in range(0, len(result)):
|
||
item = result[i]
|
||
if item['symbol'] == symbol:
|
||
return item
|
||
else:
|
||
return self.parse_open_interest(response, market)
|
||
return None
|
||
|
||
def parse_open_interest(self, interest, market: Market = None):
|
||
timestamp = self.safe_integer_2(interest, 'timestamp', 'time')
|
||
id = self.safe_string(interest, 'symbol')
|
||
amount = self.safe_number_2(interest, 'sumOpenInterest', 'openInterest')
|
||
value = self.safe_number_2(interest, 'sumOpenInterestValue', 'sumOpenInterestUsd')
|
||
# Inverse returns the number of contracts different from the base or quote hasattr(self, volume) case
|
||
# compared with https://www.binance.com/en/futures/funding-history/quarterly/4
|
||
return self.safe_open_interest({
|
||
'symbol': self.safe_symbol(id, market, None, 'contract'),
|
||
'baseVolume': None if market['inverse'] else amount, # deprecated
|
||
'quoteVolume': value, # deprecated
|
||
'openInterestAmount': amount,
|
||
'openInterestValue': value,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'info': interest,
|
||
}, market)
|
||
|
||
def fetch_my_liquidations(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
retrieves the users liquidated positions
|
||
|
||
https://developers.binance.com/docs/margin_trading/trade/Get-Force-Liquidation-Record
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Users-Force-Orders
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Users-Force-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-UM-Force-Orders
|
||
https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-CM-Force-Orders
|
||
|
||
: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 binance api endpoint
|
||
:param int [params.until]: timestamp in ms of the latest liquidation
|
||
:param boolean [params.paginate]: *spot only* default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
||
:param boolean [params.portfolioMargin]: set to True if you would like to fetch liquidations in a portfolio margin account
|
||
:param str [params.type]: "spot"
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: an array of `liquidation structures <https://docs.ccxt.com/#/?id=liquidation-structure>`
|
||
"""
|
||
self.load_markets()
|
||
paginate = False
|
||
paginate, params = self.handle_option_and_params(params, 'fetchMyLiquidations', 'paginate')
|
||
if paginate:
|
||
return self.fetch_paginated_call_incremental('fetchMyLiquidations', symbol, since, limit, params, 'current', 100)
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
type = None
|
||
type, params = self.handle_market_type_and_params('fetchMyLiquidations', market, params)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchMyLiquidations', market, params, 'linear')
|
||
isPortfolioMargin = None
|
||
isPortfolioMargin, params = self.handle_option_and_params_2(params, 'fetchMyLiquidations', 'papi', 'portfolioMargin', False)
|
||
request: dict = {}
|
||
if type != 'spot':
|
||
request['autoCloseType'] = 'LIQUIDATION'
|
||
if market is not None:
|
||
symbolKey = 'isolatedSymbol' if market['spot'] else 'symbol'
|
||
if not isPortfolioMargin:
|
||
request[symbolKey] = market['id']
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
if type == 'spot':
|
||
request['size'] = limit
|
||
else:
|
||
request['limit'] = limit
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
response = None
|
||
if type == 'spot':
|
||
if isPortfolioMargin:
|
||
response = self.papiGetMarginForceOrders(self.extend(request, params))
|
||
else:
|
||
response = self.sapiGetMarginForceLiquidationRec(self.extend(request, params))
|
||
elif subType == 'linear':
|
||
if isPortfolioMargin:
|
||
response = self.papiGetUmForceOrders(self.extend(request, params))
|
||
else:
|
||
response = self.fapiPrivateGetForceOrders(self.extend(request, params))
|
||
elif subType == 'inverse':
|
||
if isPortfolioMargin:
|
||
response = self.papiGetCmForceOrders(self.extend(request, params))
|
||
else:
|
||
response = self.dapiPrivateGetForceOrders(self.extend(request, params))
|
||
else:
|
||
raise NotSupported(self.id + ' fetchMyLiquidations() does not support ' + market['type'] + ' markets')
|
||
#
|
||
# margin
|
||
#
|
||
# {
|
||
# "rows": [
|
||
# {
|
||
# "avgPrice": "0.00388359",
|
||
# "executedQty": "31.39000000",
|
||
# "orderId": 180015097,
|
||
# "price": "0.00388110",
|
||
# "qty": "31.39000000",
|
||
# "side": "SELL",
|
||
# "symbol": "BNBBTC",
|
||
# "timeInForce": "GTC",
|
||
# "isIsolated": True,
|
||
# "updatedTime": 1558941374745
|
||
# }
|
||
# ],
|
||
# "total": 1
|
||
# }
|
||
#
|
||
# linear
|
||
#
|
||
# [
|
||
# {
|
||
# "orderId": 6071832819,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "FILLED",
|
||
# "clientOrderId": "autoclose-1596107620040000020",
|
||
# "price": "10871.09",
|
||
# "avgPrice": "10913.21000",
|
||
# "origQty": "0.001",
|
||
# "executedQty": "0.001",
|
||
# "cumQuote": "10.91321",
|
||
# "timeInForce": "IOC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "SELL",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "origType": "LIMIT",
|
||
# "time": 1596107620044,
|
||
# "updateTime": 1596107620087
|
||
# },
|
||
# ]
|
||
#
|
||
# inverse
|
||
#
|
||
# [
|
||
# {
|
||
# "orderId": 165123080,
|
||
# "symbol": "BTCUSD_200925",
|
||
# "pair": "BTCUSD",
|
||
# "status": "FILLED",
|
||
# "clientOrderId": "autoclose-1596542005017000006",
|
||
# "price": "11326.9",
|
||
# "avgPrice": "11326.9",
|
||
# "origQty": "1",
|
||
# "executedQty": "1",
|
||
# "cumBase": "0.00882854",
|
||
# "timeInForce": "IOC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "SELL",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "time": 1596542005019,
|
||
# "updateTime": 1596542005050
|
||
# },
|
||
# ]
|
||
#
|
||
liquidations = self.safe_list(response, 'rows', response)
|
||
return self.parse_liquidations(liquidations, market, since, limit)
|
||
|
||
def parse_liquidation(self, liquidation, market: Market = None):
|
||
#
|
||
# margin
|
||
#
|
||
# {
|
||
# "avgPrice": "0.00388359",
|
||
# "executedQty": "31.39000000",
|
||
# "orderId": 180015097,
|
||
# "price": "0.00388110",
|
||
# "qty": "31.39000000",
|
||
# "side": "SELL",
|
||
# "symbol": "BNBBTC",
|
||
# "timeInForce": "GTC",
|
||
# "isIsolated": True,
|
||
# "updatedTime": 1558941374745
|
||
# }
|
||
#
|
||
# linear
|
||
#
|
||
# {
|
||
# "orderId": 6071832819,
|
||
# "symbol": "BTCUSDT",
|
||
# "status": "FILLED",
|
||
# "clientOrderId": "autoclose-1596107620040000020",
|
||
# "price": "10871.09",
|
||
# "avgPrice": "10913.21000",
|
||
# "origQty": "0.001",
|
||
# "executedQty": "0.002",
|
||
# "cumQuote": "10.91321",
|
||
# "timeInForce": "IOC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "SELL",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "origType": "LIMIT",
|
||
# "time": 1596107620044,
|
||
# "updateTime": 1596107620087
|
||
# }
|
||
#
|
||
# inverse
|
||
#
|
||
# {
|
||
# "orderId": 165123080,
|
||
# "symbol": "BTCUSD_200925",
|
||
# "pair": "BTCUSD",
|
||
# "status": "FILLED",
|
||
# "clientOrderId": "autoclose-1596542005017000006",
|
||
# "price": "11326.9",
|
||
# "avgPrice": "11326.9",
|
||
# "origQty": "1",
|
||
# "executedQty": "1",
|
||
# "cumBase": "0.00882854",
|
||
# "timeInForce": "IOC",
|
||
# "type": "LIMIT",
|
||
# "reduceOnly": False,
|
||
# "closePosition": False,
|
||
# "side": "SELL",
|
||
# "positionSide": "BOTH",
|
||
# "stopPrice": "0",
|
||
# "workingType": "CONTRACT_PRICE",
|
||
# "priceProtect": False,
|
||
# "origType": "LIMIT",
|
||
# "time": 1596542005019,
|
||
# "updateTime": 1596542005050
|
||
# }
|
||
#
|
||
marketId = self.safe_string(liquidation, 'symbol')
|
||
timestamp = self.safe_integer_2(liquidation, 'updatedTime', 'updateTime')
|
||
return self.safe_liquidation({
|
||
'info': liquidation,
|
||
'symbol': self.safe_symbol(marketId, market),
|
||
'contracts': self.safe_number(liquidation, 'executedQty'),
|
||
'contractSize': self.safe_number(market, 'contractSize'),
|
||
'price': self.safe_number(liquidation, 'avgPrice'),
|
||
'side': self.safe_string_lower(liquidation, 'side'),
|
||
'baseValue': self.safe_number(liquidation, 'cumBase'),
|
||
'quoteValue': self.safe_number(liquidation, 'cumQuote'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
})
|
||
|
||
def fetch_greeks(self, symbol: str, params={}) -> Greeks:
|
||
"""
|
||
fetches an option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
|
||
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Option-Mark-Price
|
||
|
||
:param str symbol: unified symbol of the market to fetch greeks for
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = self.eapiPublicGetMark(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTC-231229-40000-C",
|
||
# "markPrice": "2012",
|
||
# "bidIV": "0.60236275",
|
||
# "askIV": "0.62267244",
|
||
# "markIV": "0.6125176",
|
||
# "delta": "0.39111646",
|
||
# "theta": "-32.13948531",
|
||
# "gamma": "0.00004656",
|
||
# "vega": "51.70062218",
|
||
# "highPriceLimit": "6474",
|
||
# "lowPriceLimit": "5"
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_greeks(response[0], market)
|
||
|
||
def fetch_all_greeks(self, symbols: Strings = None, params={}) -> List[Greeks]:
|
||
"""
|
||
fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
|
||
|
||
https://developers.binance.com/docs/derivatives/option/market-data/Option-Mark-Price
|
||
|
||
:param str[] [symbols]: unified symbols of the markets to fetch greeks for, all markets are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `greeks structure <https://docs.ccxt.com/#/?id=greeks-structure>`
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols, None, True, True, True)
|
||
request: dict = {}
|
||
market = None
|
||
if symbols is not None:
|
||
symbolsLength = len(symbols)
|
||
if symbolsLength == 1:
|
||
market = self.market(symbols[0])
|
||
request['symbol'] = market['id']
|
||
response = self.eapiPublicGetMark(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTC-231229-40000-C",
|
||
# "markPrice": "2012",
|
||
# "bidIV": "0.60236275",
|
||
# "askIV": "0.62267244",
|
||
# "markIV": "0.6125176",
|
||
# "delta": "0.39111646",
|
||
# "theta": "-32.13948531",
|
||
# "gamma": "0.00004656",
|
||
# "vega": "51.70062218",
|
||
# "highPriceLimit": "6474",
|
||
# "lowPriceLimit": "5"
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_all_greeks(response, symbols)
|
||
|
||
def parse_greeks(self, greeks: dict, market: Market = None) -> Greeks:
|
||
#
|
||
# {
|
||
# "symbol": "BTC-231229-40000-C",
|
||
# "markPrice": "2012",
|
||
# "bidIV": "0.60236275",
|
||
# "askIV": "0.62267244",
|
||
# "markIV": "0.6125176",
|
||
# "delta": "0.39111646",
|
||
# "theta": "-32.13948531",
|
||
# "gamma": "0.00004656",
|
||
# "vega": "51.70062218",
|
||
# "highPriceLimit": "6474",
|
||
# "lowPriceLimit": "5"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(greeks, 'symbol')
|
||
symbol = self.safe_symbol(marketId, market)
|
||
return {
|
||
'symbol': symbol,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'delta': self.safe_number(greeks, 'delta'),
|
||
'gamma': self.safe_number(greeks, 'gamma'),
|
||
'theta': self.safe_number(greeks, 'theta'),
|
||
'vega': self.safe_number(greeks, 'vega'),
|
||
'rho': None,
|
||
'bidSize': None,
|
||
'askSize': None,
|
||
'bidImpliedVolatility': self.safe_number(greeks, 'bidIV'),
|
||
'askImpliedVolatility': self.safe_number(greeks, 'askIV'),
|
||
'markImpliedVolatility': self.safe_number(greeks, 'markIV'),
|
||
'bidPrice': None,
|
||
'askPrice': None,
|
||
'markPrice': self.safe_number(greeks, 'markPrice'),
|
||
'lastPrice': None,
|
||
'underlyingPrice': None,
|
||
'info': greeks,
|
||
}
|
||
|
||
def fetch_trading_limits(self, symbols: Strings = None, params={}):
|
||
# self method should not be called directly, use loadTradingLimits() instead
|
||
markets = self.fetch_markets()
|
||
tradingLimits: dict = {}
|
||
for i in range(0, len(markets)):
|
||
market = markets[i]
|
||
symbol = market['symbol']
|
||
if (symbols is None) or (self.in_array(symbol, symbols)):
|
||
tradingLimits[symbol] = market['limits']['amount']
|
||
return tradingLimits
|
||
|
||
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://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Get-Current-Position-Mode
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Get-Current-Position-Mode
|
||
|
||
: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
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: an object detailing whether the market is in hedged or one-way mode
|
||
"""
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchPositionMode', market, params)
|
||
response = None
|
||
if subType == 'linear':
|
||
response = self.fapiPrivateGetPositionSideDual(params)
|
||
elif subType == 'inverse':
|
||
response = self.dapiPrivateGetPositionSideDual(params)
|
||
else:
|
||
raise BadRequest(self.id + ' fetchPositionMode requires either a symbol argument or params["subType"]')
|
||
#
|
||
# {
|
||
# dualSidePosition: False
|
||
# }
|
||
#
|
||
dualSidePosition = self.safe_bool(response, 'dualSidePosition')
|
||
return {
|
||
'info': response,
|
||
'hedged': dualSidePosition,
|
||
}
|
||
|
||
def fetch_margin_modes(self, symbols: Strings = None, params={}) -> MarginModes:
|
||
"""
|
||
fetches margin modes("isolated" or "cross") that the market for the symbol in in, with symbol=None all markets for a subType(linear/inverse) are returned
|
||
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Account-Information
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Account-Information-V2
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Symbol-Config
|
||
|
||
:param str[] symbols: unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict: a list of `margin mode structures <https://docs.ccxt.com/#/?id=margin-mode-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
if symbols is not None:
|
||
symbols = self.market_symbols(symbols)
|
||
market = self.market(symbols[0])
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchMarginMode', market, params)
|
||
response = None
|
||
if subType == 'linear':
|
||
response = self.fapiPrivateGetSymbolConfig(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "marginType": "CROSSED",
|
||
# "isAutoAddMargin": "false",
|
||
# "leverage": 21,
|
||
# "maxNotionalValue": "1000000",
|
||
# }
|
||
# ]
|
||
#
|
||
elif subType == 'inverse':
|
||
response = self.dapiPrivateGetAccount(params)
|
||
#
|
||
# {
|
||
# feeTier: '0',
|
||
# canTrade: True,
|
||
# canDeposit: True,
|
||
# canWithdraw: True,
|
||
# updateTime: '0',
|
||
# assets: [
|
||
# {
|
||
# asset: 'APT',
|
||
# walletBalance: '0.00000000',
|
||
# unrealizedProfit: '0.00000000',
|
||
# marginBalance: '0.00000000',
|
||
# maintMargin: '0.00000000',
|
||
# initialMargin: '0.00000000',
|
||
# positionInitialMargin: '0.00000000',
|
||
# openOrderInitialMargin: '0.00000000',
|
||
# maxWithdrawAmount: '0.00000000',
|
||
# crossWalletBalance: '0.00000000',
|
||
# crossUnPnl: '0.00000000',
|
||
# availableBalance: '0.00000000',
|
||
# updateTime: '0'
|
||
# },
|
||
# ...
|
||
# ],
|
||
# positions: [
|
||
# {
|
||
# symbol: 'BCHUSD_240329',
|
||
# initialMargin: '0',
|
||
# maintMargin: '0',
|
||
# unrealizedProfit: '0.00000000',
|
||
# positionInitialMargin: '0',
|
||
# openOrderInitialMargin: '0',
|
||
# leverage: '20',
|
||
# isolated: False,
|
||
# positionSide: 'BOTH',
|
||
# entryPrice: '0.00000000',
|
||
# maxQty: '1000',
|
||
# notionalValue: '0',
|
||
# isolatedWallet: '0',
|
||
# updateTime: '0',
|
||
# positionAmt: '0',
|
||
# breakEvenPrice: '0.00000000'
|
||
# },
|
||
# ...
|
||
# ]
|
||
# }
|
||
#
|
||
else:
|
||
raise BadRequest(self.id + ' fetchMarginModes() supports linear and inverse subTypes only')
|
||
assets = self.safe_list(response, 'positions', [])
|
||
if isinstance(response, list):
|
||
assets = response
|
||
return self.parse_margin_modes(assets, symbols, 'symbol', 'swap')
|
||
|
||
def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode:
|
||
"""
|
||
fetches the margin mode of a specific symbol
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Symbol-Config
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/account/rest-api/Account-Information
|
||
|
||
: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.subType]: "linear" or "inverse"
|
||
:returns dict: a `margin mode structure <https://docs.ccxt.com/#/?id=margin-mode-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchMarginMode', market, params)
|
||
response = None
|
||
if subType == 'linear':
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = self.fapiPrivateGetSymbolConfig(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "marginType": "CROSSED",
|
||
# "isAutoAddMargin": "false",
|
||
# "leverage": 21,
|
||
# "maxNotionalValue": "1000000",
|
||
# }
|
||
# ]
|
||
#
|
||
elif subType == 'inverse':
|
||
fetchMarginModesResponse = self.fetch_margin_modes([symbol], params)
|
||
return fetchMarginModesResponse[symbol]
|
||
else:
|
||
raise BadRequest(self.id + ' fetchMarginMode() supports linear and inverse subTypes only')
|
||
return self.parse_margin_mode(response[0], market)
|
||
|
||
def parse_margin_mode(self, marginMode: dict, market=None) -> MarginMode:
|
||
marketId = self.safe_string(marginMode, 'symbol')
|
||
market = self.safe_market(marketId, market)
|
||
marginModeRaw = self.safe_bool(marginMode, 'isolated')
|
||
reMarginMode = None
|
||
if marginModeRaw is not None:
|
||
reMarginMode = 'isolated' if marginModeRaw else 'cross'
|
||
marginTypeRaw = self.safe_string_lower(marginMode, 'marginType')
|
||
if marginTypeRaw is not None:
|
||
reMarginMode = 'cross' if (marginTypeRaw == 'crossed') else 'isolated'
|
||
return {
|
||
'info': marginMode,
|
||
'symbol': market['symbol'],
|
||
'marginMode': reMarginMode,
|
||
}
|
||
|
||
def fetch_option(self, symbol: str, params={}) -> Option:
|
||
"""
|
||
fetches option data that is commonly found in an option chain
|
||
|
||
https://developers.binance.com/docs/derivatives/option/market-data/24hr-Ticker-Price-Change-Statistics
|
||
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `option chain structure <https://docs.ccxt.com/#/?id=option-chain-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
response = self.eapiPublicGetTicker(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTC-241227-80000-C",
|
||
# "priceChange": "0",
|
||
# "priceChangePercent": "0",
|
||
# "lastPrice": "2750",
|
||
# "lastQty": "0",
|
||
# "open": "2750",
|
||
# "high": "2750",
|
||
# "low": "2750",
|
||
# "volume": "0",
|
||
# "amount": "0",
|
||
# "bidPrice": "4880",
|
||
# "askPrice": "0",
|
||
# "openTime": 0,
|
||
# "closeTime": 0,
|
||
# "firstTradeId": 0,
|
||
# "tradeCount": 0,
|
||
# "strikePrice": "80000",
|
||
# "exercisePrice": "63944.09893617"
|
||
# }
|
||
# ]
|
||
#
|
||
chain = self.safe_dict(response, 0, {})
|
||
return self.parse_option(chain, None, market)
|
||
|
||
def parse_option(self, chain: dict, currency: Currency = None, market: Market = None) -> Option:
|
||
#
|
||
# {
|
||
# "symbol": "BTC-241227-80000-C",
|
||
# "priceChange": "0",
|
||
# "priceChangePercent": "0",
|
||
# "lastPrice": "2750",
|
||
# "lastQty": "0",
|
||
# "open": "2750",
|
||
# "high": "2750",
|
||
# "low": "2750",
|
||
# "volume": "0",
|
||
# "amount": "0",
|
||
# "bidPrice": "4880",
|
||
# "askPrice": "0",
|
||
# "openTime": 0,
|
||
# "closeTime": 0,
|
||
# "firstTradeId": 0,
|
||
# "tradeCount": 0,
|
||
# "strikePrice": "80000",
|
||
# "exercisePrice": "63944.09893617"
|
||
# }
|
||
#
|
||
marketId = self.safe_string(chain, 'symbol')
|
||
market = self.safe_market(marketId, market)
|
||
return {
|
||
'info': chain,
|
||
'currency': None,
|
||
'symbol': market['symbol'],
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
'impliedVolatility': None,
|
||
'openInterest': None,
|
||
'bidPrice': self.safe_number(chain, 'bidPrice'),
|
||
'askPrice': self.safe_number(chain, 'askPrice'),
|
||
'midPrice': None,
|
||
'markPrice': None,
|
||
'lastPrice': self.safe_number(chain, 'lastPrice'),
|
||
'underlyingPrice': self.safe_number(chain, 'exercisePrice'),
|
||
'change': self.safe_number(chain, 'priceChange'),
|
||
'percentage': self.safe_number(chain, 'priceChangePercent'),
|
||
'baseVolume': self.safe_number(chain, 'volume'),
|
||
'quoteVolume': None,
|
||
}
|
||
|
||
def fetch_margin_adjustment_history(self, symbol: Str = None, type: Str = None, since: Num = None, limit: Num = None, params={}) -> List[MarginModification]:
|
||
"""
|
||
fetches the history of margin added or reduced from contract isolated positions
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/rest-api/Get-Position-Margin-Change-History
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/trade/rest-api/Get-Position-Margin-Change-History
|
||
|
||
:param str symbol: unified market symbol
|
||
:param str [type]: "add" or "reduce"
|
||
:param int [since]: timestamp in ms of the earliest change to fetch
|
||
:param int [limit]: the maximum amount of changes to fetch
|
||
:param dict params: extra parameters specific to the exchange api endpoint
|
||
:param int [params.until]: timestamp in ms of the latest change to fetch
|
||
:returns dict[]: a list of `margin structures <https://docs.ccxt.com/#/?id=margin-loan-structure>`
|
||
"""
|
||
self.load_markets()
|
||
if symbol is None:
|
||
raise ArgumentsRequired(self.id + ' fetchMarginAdjustmentHistory() requires a symbol argument')
|
||
market = self.market(symbol)
|
||
until = self.safe_integer(params, 'until')
|
||
params = self.omit(params, 'until')
|
||
request: dict = {
|
||
'symbol': market['id'],
|
||
}
|
||
if type is not None:
|
||
request['type'] = 1 if (type == 'add') else 2
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
if until is not None:
|
||
request['endTime'] = until
|
||
response = None
|
||
if market['linear']:
|
||
response = self.fapiPrivateGetPositionMarginHistory(self.extend(request, params))
|
||
elif market['inverse']:
|
||
response = self.dapiPrivateGetPositionMarginHistory(self.extend(request, params))
|
||
else:
|
||
raise BadRequest(self.id + ' fetchMarginAdjustmentHistory() is not supported for markets of type ' + market['type'])
|
||
#
|
||
# [
|
||
# {
|
||
# symbol: "XRPUSDT",
|
||
# type: "1",
|
||
# deltaType: "TRADE",
|
||
# amount: "2.57148240",
|
||
# asset: "USDT",
|
||
# time: "1711046271555",
|
||
# positionSide: "BOTH",
|
||
# clientTranId: ""
|
||
# }
|
||
# ...
|
||
# ]
|
||
#
|
||
modifications = self.parse_margin_modifications(response)
|
||
return self.filter_by_symbol_since_limit(modifications, symbol, since, limit)
|
||
|
||
def fetch_convert_currencies(self, params={}) -> Currencies:
|
||
"""
|
||
fetches all available currencies that can be converted
|
||
|
||
https://developers.binance.com/docs/convert/market-data/Query-order-quantity-precision-per-asset
|
||
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an associative dictionary of currencies
|
||
"""
|
||
self.load_markets()
|
||
response = self.sapiGetConvertAssetInfo(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "asset": "BTC",
|
||
# "fraction": 8
|
||
# },
|
||
# ]
|
||
#
|
||
result: dict = {}
|
||
for i in range(0, len(response)):
|
||
entry = response[i]
|
||
id = self.safe_string(entry, 'asset')
|
||
code = self.safe_currency_code(id)
|
||
result[code] = {
|
||
'info': entry,
|
||
'id': id,
|
||
'code': code,
|
||
'networks': None,
|
||
'type': None,
|
||
'name': None,
|
||
'active': None,
|
||
'deposit': None,
|
||
'withdraw': None,
|
||
'fee': None,
|
||
'precision': self.parse_number(self.parse_precision(self.safe_string(entry, 'fraction'))),
|
||
'limits': {
|
||
'amount': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'withdraw': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'deposit': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': None,
|
||
}
|
||
return result
|
||
|
||
def fetch_convert_quote(self, fromCode: str, toCode: str, amount: Num = None, params={}) -> Conversion:
|
||
"""
|
||
fetch a quote for converting from one currency to another
|
||
|
||
https://developers.binance.com/docs/convert/trade/Send-quote-request
|
||
|
||
:param str fromCode: the currency that you want to sell and convert from
|
||
:param str toCode: the currency that you want to buy and convert into
|
||
:param float amount: how much you want to trade in units of the from currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.walletType]: either 'SPOT' or 'FUNDING', the default is 'SPOT'
|
||
:returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
if amount is None:
|
||
raise ArgumentsRequired(self.id + ' fetchConvertQuote() requires an amount argument')
|
||
self.load_markets()
|
||
request: dict = {
|
||
'fromAsset': fromCode,
|
||
'toAsset': toCode,
|
||
'fromAmount': amount,
|
||
}
|
||
response = self.sapiPostConvertGetQuote(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "quoteId":"12415572564",
|
||
# "ratio":"38163.7",
|
||
# "inverseRatio":"0.0000262",
|
||
# "validTimestamp":1623319461670,
|
||
# "toAmount":"3816.37",
|
||
# "fromAmount":"0.1"
|
||
# }
|
||
#
|
||
fromCurrency = self.currency(fromCode)
|
||
toCurrency = self.currency(toCode)
|
||
return self.parse_conversion(response, fromCurrency, toCurrency)
|
||
|
||
def create_convert_trade(self, id: str, fromCode: str, toCode: str, amount: Num = None, params={}) -> Conversion:
|
||
"""
|
||
convert from one currency to another
|
||
|
||
https://developers.binance.com/docs/convert/trade/Accept-Quote
|
||
|
||
:param str id: the id of the trade that you want to make
|
||
:param str fromCode: the currency that you want to sell and convert from
|
||
:param str toCode: the currency that you want to buy and convert into
|
||
:param float [amount]: how much you want to trade in units of the from currency
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
response = None
|
||
if (fromCode == 'BUSD') or (toCode == 'BUSD'):
|
||
if amount is None:
|
||
raise ArgumentsRequired(self.id + ' createConvertTrade() requires an amount argument')
|
||
request['clientTranId'] = id
|
||
request['asset'] = fromCode
|
||
request['targetAsset'] = toCode
|
||
request['amount'] = amount
|
||
response = self.sapiPostAssetConvertTransfer(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "tranId": 118263407119,
|
||
# "status": "S"
|
||
# }
|
||
#
|
||
else:
|
||
request['quoteId'] = id
|
||
response = self.sapiPostConvertAcceptQuote(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "orderId":"933256278426274426",
|
||
# "createTime":1623381330472,
|
||
# "orderStatus":"PROCESS"
|
||
# }
|
||
#
|
||
fromCurrency = self.currency(fromCode)
|
||
toCurrency = self.currency(toCode)
|
||
return self.parse_conversion(response, fromCurrency, toCurrency)
|
||
|
||
def fetch_convert_trade(self, id: str, code: Str = None, params={}) -> Conversion:
|
||
"""
|
||
fetch the data for a conversion trade
|
||
|
||
https://developers.binance.com/docs/convert/trade/Order-Status
|
||
|
||
:param str id: the id of the trade that you want to fetch
|
||
:param str [code]: the unified currency code of the conversion trade
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
response = None
|
||
if code == 'BUSD':
|
||
msInDay = 86400000
|
||
now = self.milliseconds()
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['asset'] = currency['id']
|
||
request['tranId'] = id
|
||
request['startTime'] = now - msInDay
|
||
request['endTime'] = now
|
||
response = self.sapiGetAssetConvertTransferQueryByPage(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "total": 3,
|
||
# "rows": [
|
||
# {
|
||
# "tranId": 118263615991,
|
||
# "type": 244,
|
||
# "time": 1664442078000,
|
||
# "deductedAsset": "BUSD",
|
||
# "deductedAmount": "1",
|
||
# "targetAsset": "USDC",
|
||
# "targetAmount": "1",
|
||
# "status": "S",
|
||
# "accountType": "MAIN"
|
||
# },
|
||
# ]
|
||
# }
|
||
#
|
||
else:
|
||
request['orderId'] = id
|
||
response = self.sapiGetConvertOrderStatus(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "orderId":933256278426274426,
|
||
# "orderStatus":"SUCCESS",
|
||
# "fromAsset":"BTC",
|
||
# "fromAmount":"0.00054414",
|
||
# "toAsset":"USDT",
|
||
# "toAmount":"20",
|
||
# "ratio":"36755",
|
||
# "inverseRatio":"0.00002721",
|
||
# "createTime":1623381330472
|
||
# }
|
||
#
|
||
data = response
|
||
if code == 'BUSD':
|
||
rows = self.safe_list(response, 'rows', [])
|
||
data = self.safe_dict(rows, 0, {})
|
||
fromCurrencyId = self.safe_string_2(data, 'deductedAsset', 'fromAsset')
|
||
toCurrencyId = self.safe_string_2(data, 'targetAsset', 'toAsset')
|
||
fromCurrency = None
|
||
toCurrency = None
|
||
if fromCurrencyId is not None:
|
||
fromCurrency = self.currency(fromCurrencyId)
|
||
if toCurrencyId is not None:
|
||
toCurrency = self.currency(toCurrencyId)
|
||
return self.parse_conversion(data, fromCurrency, toCurrency)
|
||
|
||
def fetch_convert_trade_history(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Conversion]:
|
||
"""
|
||
fetch the users history of conversion trades
|
||
|
||
https://developers.binance.com/docs/convert/trade/Get-Convert-Trade-History
|
||
|
||
:param str [code]: the unified currency code
|
||
:param int [since]: the earliest time in ms to fetch conversions for
|
||
:param int [limit]: the maximum number of conversion structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: timestamp in ms of the latest conversion to fetch
|
||
:returns dict[]: a list of `conversion structures <https://docs.ccxt.com/#/?id=conversion-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
msInThirtyDays = 2592000000
|
||
now = self.milliseconds()
|
||
if since is not None:
|
||
request['startTime'] = since
|
||
else:
|
||
request['startTime'] = now - msInThirtyDays
|
||
endTime = self.safe_integer_2(params, 'endTime', 'until')
|
||
if endTime is not None:
|
||
request['endTime'] = endTime
|
||
else:
|
||
request['endTime'] = now
|
||
params = self.omit(params, 'until')
|
||
response = None
|
||
responseQuery = None
|
||
fromCurrencyKey = None
|
||
toCurrencyKey = None
|
||
if code == 'BUSD':
|
||
currency = self.currency(code)
|
||
request['asset'] = currency['id']
|
||
if limit is not None:
|
||
request['size'] = limit
|
||
fromCurrencyKey = 'deductedAsset'
|
||
toCurrencyKey = 'targetAsset'
|
||
responseQuery = 'rows'
|
||
response = self.sapiGetAssetConvertTransferQueryByPage(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "total": 3,
|
||
# "rows": [
|
||
# {
|
||
# "tranId": 118263615991,
|
||
# "type": 244,
|
||
# "time": 1664442078000,
|
||
# "deductedAsset": "BUSD",
|
||
# "deductedAmount": "1",
|
||
# "targetAsset": "USDC",
|
||
# "targetAmount": "1",
|
||
# "status": "S",
|
||
# "accountType": "MAIN"
|
||
# },
|
||
# ]
|
||
# }
|
||
#
|
||
else:
|
||
if (request['endTime'] - request['startTime']) > msInThirtyDays:
|
||
raise BadRequest(self.id + ' fetchConvertTradeHistory() the max interval between startTime and endTime is 30 days.')
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
fromCurrencyKey = 'fromAsset'
|
||
toCurrencyKey = 'toAsset'
|
||
responseQuery = 'list'
|
||
response = self.sapiGetConvertTradeFlow(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "list": [
|
||
# {
|
||
# "quoteId": "f3b91c525b2644c7bc1e1cd31b6e1aa6",
|
||
# "orderId": 940708407462087195,
|
||
# "orderStatus": "SUCCESS",
|
||
# "fromAsset": "USDT",
|
||
# "fromAmount": "20",
|
||
# "toAsset": "BNB",
|
||
# "toAmount": "0.06154036",
|
||
# "ratio": "0.00307702",
|
||
# "inverseRatio": "324.99",
|
||
# "createTime": 1624248872184
|
||
# }
|
||
# ],
|
||
# "startTime": 1623824139000,
|
||
# "endTime": 1626416139000,
|
||
# "limit": 100,
|
||
# "moreData": False
|
||
# }
|
||
#
|
||
rows = self.safe_list(response, responseQuery, [])
|
||
return self.parse_conversions(rows, code, fromCurrencyKey, toCurrencyKey, since, limit)
|
||
|
||
def parse_conversion(self, conversion: dict, fromCurrency: Currency = None, toCurrency: Currency = None) -> Conversion:
|
||
#
|
||
# fetchConvertQuote
|
||
#
|
||
# {
|
||
# "quoteId":"12415572564",
|
||
# "ratio":"38163.7",
|
||
# "inverseRatio":"0.0000262",
|
||
# "validTimestamp":1623319461670,
|
||
# "toAmount":"3816.37",
|
||
# "fromAmount":"0.1"
|
||
# }
|
||
#
|
||
# createConvertTrade
|
||
#
|
||
# {
|
||
# "orderId":"933256278426274426",
|
||
# "createTime":1623381330472,
|
||
# "orderStatus":"PROCESS"
|
||
# }
|
||
#
|
||
# createConvertTrade BUSD
|
||
#
|
||
# {
|
||
# "tranId": 118263407119,
|
||
# "status": "S"
|
||
# }
|
||
#
|
||
# fetchConvertTrade, fetchConvertTradeHistory BUSD
|
||
#
|
||
# {
|
||
# "tranId": 118263615991,
|
||
# "type": 244,
|
||
# "time": 1664442078000,
|
||
# "deductedAsset": "BUSD",
|
||
# "deductedAmount": "1",
|
||
# "targetAsset": "USDC",
|
||
# "targetAmount": "1",
|
||
# "status": "S",
|
||
# "accountType": "MAIN"
|
||
# }
|
||
#
|
||
# fetchConvertTrade
|
||
#
|
||
# {
|
||
# "orderId":933256278426274426,
|
||
# "orderStatus":"SUCCESS",
|
||
# "fromAsset":"BTC",
|
||
# "fromAmount":"0.00054414",
|
||
# "toAsset":"USDT",
|
||
# "toAmount":"20",
|
||
# "ratio":"36755",
|
||
# "inverseRatio":"0.00002721",
|
||
# "createTime":1623381330472
|
||
# }
|
||
#
|
||
# fetchConvertTradeHistory
|
||
#
|
||
# {
|
||
# "quoteId": "f3b91c525b2644c7bc1e1cd31b6e1aa6",
|
||
# "orderId": 940708407462087195,
|
||
# "orderStatus": "SUCCESS",
|
||
# "fromAsset": "USDT",
|
||
# "fromAmount": "20",
|
||
# "toAsset": "BNB",
|
||
# "toAmount": "0.06154036",
|
||
# "ratio": "0.00307702",
|
||
# "inverseRatio": "324.99",
|
||
# "createTime": 1624248872184
|
||
# }
|
||
#
|
||
timestamp = self.safe_integer_n(conversion, ['time', 'validTimestamp', 'createTime'])
|
||
fromCur = self.safe_string_2(conversion, 'deductedAsset', 'fromAsset')
|
||
fromCode = self.safe_currency_code(fromCur, fromCurrency)
|
||
to = self.safe_string_2(conversion, 'targetAsset', 'toAsset')
|
||
toCode = self.safe_currency_code(to, toCurrency)
|
||
return {
|
||
'info': conversion,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'id': self.safe_string_n(conversion, ['tranId', 'orderId', 'quoteId']),
|
||
'fromCurrency': fromCode,
|
||
'fromAmount': self.safe_number_2(conversion, 'deductedAmount', 'fromAmount'),
|
||
'toCurrency': toCode,
|
||
'toAmount': self.safe_number_2(conversion, 'targetAmount', 'toAmount'),
|
||
'price': None,
|
||
'fee': None,
|
||
}
|
||
|
||
def fetch_funding_intervals(self, symbols: Strings = None, params={}) -> FundingRates:
|
||
"""
|
||
fetch the funding rate interval for multiple markets
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Get-Funding-Rate-Info
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Get-Funding-Info
|
||
|
||
:param str[] [symbols]: list of unified market symbols
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.subType]: "linear" or "inverse"
|
||
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = None
|
||
if symbols is not None:
|
||
symbols = self.market_symbols(symbols)
|
||
market = self.market(symbols[0])
|
||
type = 'swap'
|
||
subType = None
|
||
subType, params = self.handle_sub_type_and_params('fetchFundingIntervals', market, params, 'linear')
|
||
response = None
|
||
if self.is_linear(type, subType):
|
||
response = self.fapiPublicGetFundingInfo(params)
|
||
elif self.is_inverse(type, subType):
|
||
response = self.dapiPublicGetFundingInfo(params)
|
||
else:
|
||
raise NotSupported(self.id + ' fetchFundingIntervals() supports linear and inverse swap contracts only')
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BLZUSDT",
|
||
# "adjustedFundingRateCap": "0.03000000",
|
||
# "adjustedFundingRateFloor": "-0.03000000",
|
||
# "fundingIntervalHours": 4,
|
||
# "disclaimer": False
|
||
# },
|
||
# ]
|
||
#
|
||
return self.parse_funding_rates(response, symbols)
|
||
|
||
def fetch_long_short_ratio_history(self, symbol: Str = None, timeframe: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LongShortRatio]:
|
||
"""
|
||
fetches the long short ratio history for a unified market symbol
|
||
|
||
https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/rest-api/Long-Short-Ratio
|
||
https://developers.binance.com/docs/derivatives/coin-margined-futures/market-data/rest-api/Long-Short-Ratio
|
||
|
||
:param str symbol: unified symbol of the market to fetch the long short ratio for
|
||
:param str [timeframe]: the period for the ratio, default is 24 hours
|
||
:param int [since]: the earliest time in ms to fetch ratios for
|
||
:param int [limit]: the maximum number of long short ratio structures to retrieve
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: timestamp in ms of the latest ratio to fetch
|
||
:returns dict[]: an array of `long short ratio structures <https://docs.ccxt.com/#/?id=long-short-ratio-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if timeframe is None:
|
||
timeframe = '1d'
|
||
request: dict = {
|
||
'period': timeframe,
|
||
}
|
||
request, params = self.handle_until_option('endTime', request, params)
|
||
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('fetchLongShortRatioHistory', market, params)
|
||
response = None
|
||
if subType == 'linear':
|
||
request['symbol'] = market['id']
|
||
response = self.fapiDataGetGlobalLongShortAccountRatio(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "longAccount": "0.4558",
|
||
# "longShortRatio": "0.8376",
|
||
# "shortAccount": "0.5442",
|
||
# "timestamp": 1726790400000
|
||
# },
|
||
# ]
|
||
#
|
||
elif subType == 'inverse':
|
||
request['pair'] = market['info']['pair']
|
||
response = self.dapiDataGetGlobalLongShortAccountRatio(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "longAccount": "0.7262",
|
||
# "longShortRatio": "2.6523",
|
||
# "shortAccount": "0.2738",
|
||
# "pair": "BTCUSD",
|
||
# "timestamp": 1726790400000
|
||
# },
|
||
# ]
|
||
#
|
||
else:
|
||
raise BadRequest(self.id + ' fetchLongShortRatioHistory() supports linear and inverse subTypes only')
|
||
return self.parse_long_short_ratio_history(response, market)
|
||
|
||
def parse_long_short_ratio(self, info: dict, market: Market = None) -> LongShortRatio:
|
||
#
|
||
# linear
|
||
#
|
||
# {
|
||
# "symbol": "BTCUSDT",
|
||
# "longAccount": "0.4558",
|
||
# "longShortRatio": "0.8376",
|
||
# "shortAccount": "0.5442",
|
||
# "timestamp": 1726790400000
|
||
# }
|
||
#
|
||
# inverse
|
||
#
|
||
# {
|
||
# "longAccount": "0.7262",
|
||
# "longShortRatio": "2.6523",
|
||
# "shortAccount": "0.2738",
|
||
# "pair": "BTCUSD",
|
||
# "timestamp": 1726790400000
|
||
# }
|
||
#
|
||
marketId = self.safe_string(info, 'symbol')
|
||
timestamp = self.safe_integer_omit_zero(info, 'timestamp')
|
||
return {
|
||
'info': info,
|
||
'symbol': self.safe_symbol(marketId, market, None, 'contract'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'timeframe': None,
|
||
'longShortRatio': self.safe_number(info, 'longShortRatio'),
|
||
}
|