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

4917 lines
212 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
from ccxt.base.exchange import Exchange
from ccxt.abstract.xt import ImplicitAPI
import hashlib
from ccxt.base.types import Any, Currencies, Currency, DepositAddress, Int, LedgerEntry, LeverageTier, LeverageTiers, MarginModification, Market, Num, Order, OrderSide, OrderType, Position, Str, Tickers, FundingRate, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import NotSupported
from ccxt.base.errors import NetworkError
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise
class xt(Exchange, ImplicitAPI):
def describe(self) -> Any:
return self.deep_extend(super(xt, self).describe(), {
'id': 'xt',
'name': 'XT',
'countries': ['SC'], # Seychelles
# spot api ratelimits are None, 10/s/ip, 50/s/ip, 100/s/ip or 200/s/ip
# futures 3 requests per second => 1000ms / (100 * 3.33) = 3.003(get assets -> fetchMarkets & fetchCurrencies)
# futures 10 requests per second => 1000ms / (100 * 1) = 10(all other)
# futures 1000 times per minute for each single IP -> Otherwise account locked for 10min
'rateLimit': 100,
'version': 'v4',
'certified': False,
'pro': True,
'has': {
'CORS': False,
'spot': True,
'margin': True,
'swap': True,
'future': True,
'option': False,
'addMargin': True,
'borrowMargin': False,
'cancelAllOrders': True,
'cancelOrder': True,
'cancelOrders': True,
'createDepositAddress': False,
'createMarketBuyOrderWithCost': True,
'createMarketSellOrderWithCost': False,
'createOrder': True,
'createPostOnlyOrder': False,
'createReduceOnlyOrder': True,
'editOrder': True,
'fetchAccounts': False,
'fetchBalance': True,
'fetchBidsAsks': True,
'fetchBorrowInterest': False,
'fetchBorrowRate': False,
'fetchBorrowRateHistories': False,
'fetchBorrowRateHistory': False,
'fetchBorrowRatesPerSymbol': False,
'fetchCanceledOrders': True,
'fetchClosedOrders': True,
'fetchCurrencies': True,
'fetchDeposit': False,
'fetchDepositAddress': True,
'fetchDepositAddresses': False,
'fetchDepositAddressesByNetwork': False,
'fetchDeposits': True,
'fetchDepositWithdrawals': False,
'fetchDepositWithdrawFee': False,
'fetchDepositWithdrawFees': False,
'fetchFundingHistory': True,
'fetchFundingInterval': True,
'fetchFundingIntervals': False,
'fetchFundingRate': True,
'fetchFundingRateHistory': True,
'fetchFundingRates': False,
'fetchIndexOHLCV': False,
'fetchL3OrderBook': False,
'fetchLedger': True,
'fetchLedgerEntry': False,
'fetchLeverage': False,
'fetchLeverageTiers': True,
'fetchMarketLeverageTiers': True,
'fetchMarkets': True,
'fetchMarkOHLCV': False,
'fetchMyTrades': True,
'fetchOHLCV': True,
'fetchOpenInterest': False,
'fetchOpenInterestHistory': False,
'fetchOpenOrders': True,
'fetchOrder': True,
'fetchOrderBook': True,
'fetchOrderBooks': False,
'fetchOrders': True,
'fetchOrdersByStatus': True,
'fetchOrderTrades': False,
'fetchPosition': True,
'fetchPositions': True,
'fetchPremiumIndexOHLCV': False,
'fetchSettlementHistory': False,
'fetchStatus': False,
'fetchTicker': True,
'fetchTickers': True,
'fetchTime': True,
'fetchTrades': True,
'fetchTradingFee': False,
'fetchTradingFees': False,
'fetchTradingLimits': False,
'fetchTransactionFee': False,
'fetchTransactionFees': False,
'fetchTransactions': False,
'fetchTransfer': False,
'fetchTransfers': False,
'fetchWithdrawal': False,
'fetchWithdrawals': True,
'fetchWithdrawalWhitelist': False,
'reduceMargin': True,
'repayMargin': False,
'setLeverage': True,
'setMargin': False,
'setMarginMode': True,
'setPositionMode': False,
'signIn': False,
'transfer': True,
'withdraw': True,
},
'precisionMode': TICK_SIZE,
'urls': {
'logo': 'https://user-images.githubusercontent.com/14319357/232636712-466df2fc-560a-4ca4-aab2-b1d954a58e24.jpg',
'api': {
'spot': 'https://sapi.xt.com',
'linear': 'https://fapi.xt.com',
'inverse': 'https://dapi.xt.com',
'user': 'https://api.xt.com',
},
'www': 'https://xt.com',
'referral': 'https://www.xt.com/en/accounts/register?ref=9PTM9VW',
'doc': [
'https://doc.xt.com/',
'https://github.com/xtpub/api-doc',
],
'fees': 'https://www.xt.com/en/rate',
},
'api': {
'public': {
'spot': {
'get': {
'currencies': 1,
'depth': 10,
'kline': 1,
'symbol': 1, # 1 for a single symbol
'ticker': 1, # 1 for a single symbol
'ticker/book': 1, # 1 for a single symbol
'ticker/price': 1, # 1 for a single symbol
'ticker/24h': 1, # 1 for a single symbol
'time': 1,
'trade/history': 1,
'trade/recent': 1,
'wallet/support/currency': 1,
},
},
'linear': {
'get': {
'future/market/v1/public/contract/risk-balance': 1,
'future/market/v1/public/contract/open-interest': 1,
'future/market/v1/public/leverage/bracket/detail': 1,
'future/market/v1/public/leverage/bracket/list': 1,
'future/market/v1/public/q/agg-ticker': 1,
'future/market/v1/public/q/agg-tickers': 1,
'future/market/v1/public/q/deal': 1,
'future/market/v1/public/q/depth': 1,
'future/market/v1/public/q/funding-rate': 1,
'future/market/v1/public/q/funding-rate-record': 1,
'future/market/v1/public/q/index-price': 1,
'future/market/v1/public/q/kline': 1,
'future/market/v1/public/q/mark-price': 1,
'future/market/v1/public/q/symbol-index-price': 1,
'future/market/v1/public/q/symbol-mark-price': 1,
'future/market/v1/public/q/ticker': 1,
'future/market/v1/public/q/tickers': 1,
'future/market/v1/public/symbol/coins': 3.33,
'future/market/v1/public/symbol/detail': 3.33,
'future/market/v1/public/symbol/list': 1,
},
},
'inverse': {
'get': {
'future/market/v1/public/contract/risk-balance': 1,
'future/market/v1/public/contract/open-interest': 1,
'future/market/v1/public/leverage/bracket/detail': 1,
'future/market/v1/public/leverage/bracket/list': 1,
'future/market/v1/public/q/agg-ticker': 1,
'future/market/v1/public/q/agg-tickers': 1,
'future/market/v1/public/q/deal': 1,
'future/market/v1/public/q/depth': 1,
'future/market/v1/public/q/funding-rate': 1,
'future/market/v1/public/q/funding-rate-record': 1,
'future/market/v1/public/q/index-price': 1,
'future/market/v1/public/q/kline': 1,
'future/market/v1/public/q/mark-price': 1,
'future/market/v1/public/q/symbol-index-price': 1,
'future/market/v1/public/q/symbol-mark-price': 1,
'future/market/v1/public/q/ticker': 1,
'future/market/v1/public/q/tickers': 1,
'future/market/v1/public/symbol/coins': 3.33,
'future/market/v1/public/symbol/detail': 3.33,
'future/market/v1/public/symbol/list': 1,
},
},
},
'private': {
'spot': {
'get': {
'balance': 1,
'balances': 1,
'batch-order': 1,
'deposit/address': 1,
'deposit/history': 1,
'history-order': 1,
'open-order': 1,
'order': 1,
'order/{orderId}': 1,
'trade': 1,
'withdraw/history': 1,
},
'post': {
'order': 0.2,
'withdraw': 10,
'balance/transfer': 1,
'balance/account/transfer': 1,
'ws-token': 1,
},
'delete': {
'batch-order': 1,
'open-order': 1,
'order/{orderId}': 1,
},
'put': {
'order/{orderId}': 1,
},
},
'linear': {
'get': {
'future/trade/v1/entrust/plan-detail': 1,
'future/trade/v1/entrust/plan-list': 1,
'future/trade/v1/entrust/plan-list-history': 1,
'future/trade/v1/entrust/profit-detail': 1,
'future/trade/v1/entrust/profit-list': 1,
'future/trade/v1/order/detail': 1,
'future/trade/v1/order/list': 1,
'future/trade/v1/order/list-history': 1,
'future/trade/v1/order/trade-list': 1,
'future/user/v1/account/info': 1,
'future/user/v1/balance/bills': 1,
'future/user/v1/balance/detail': 1,
'future/user/v1/balance/funding-rate-list': 1,
'future/user/v1/balance/list': 1,
'future/user/v1/position/adl': 1,
'future/user/v1/position/list': 1,
'future/user/v1/user/collection/list': 1,
'future/user/v1/user/listen-key': 1,
},
'post': {
'future/trade/v1/entrust/cancel-all-plan': 1,
'future/trade/v1/entrust/cancel-all-profit-stop': 1,
'future/trade/v1/entrust/cancel-plan': 1,
'future/trade/v1/entrust/cancel-profit-stop': 1,
'future/trade/v1/entrust/create-plan': 1,
'future/trade/v1/entrust/create-profit': 1,
'future/trade/v1/entrust/update-profit-stop': 1,
'future/trade/v1/order/cancel': 1,
'future/trade/v1/order/cancel-all': 1,
'future/trade/v1/order/create': 1,
'future/trade/v1/order/create-batch': 1,
'future/trade/v1/order/update': 1,
'future/user/v1/account/open': 1,
'future/user/v1/position/adjust-leverage': 1,
'future/user/v1/position/auto-margin': 1,
'future/user/v1/position/close-all': 1,
'future/user/v1/position/margin': 1,
'future/user/v1/user/collection/add': 1,
'future/user/v1/user/collection/cancel': 1,
'future/user/v1/position/change-type': 1,
},
},
'inverse': {
'get': {
'future/trade/v1/entrust/plan-detail': 1,
'future/trade/v1/entrust/plan-list': 1,
'future/trade/v1/entrust/plan-list-history': 1,
'future/trade/v1/entrust/profit-detail': 1,
'future/trade/v1/entrust/profit-list': 1,
'future/trade/v1/order/detail': 1,
'future/trade/v1/order/list': 1,
'future/trade/v1/order/list-history': 1,
'future/trade/v1/order/trade-list': 1,
'future/user/v1/account/info': 1,
'future/user/v1/balance/bills': 1,
'future/user/v1/balance/detail': 1,
'future/user/v1/balance/funding-rate-list': 1,
'future/user/v1/balance/list': 1,
'future/user/v1/position/adl': 1,
'future/user/v1/position/list': 1,
'future/user/v1/user/collection/list': 1,
'future/user/v1/user/listen-key': 1,
},
'post': {
'future/trade/v1/entrust/cancel-all-plan': 1,
'future/trade/v1/entrust/cancel-all-profit-stop': 1,
'future/trade/v1/entrust/cancel-plan': 1,
'future/trade/v1/entrust/cancel-profit-stop': 1,
'future/trade/v1/entrust/create-plan': 1,
'future/trade/v1/entrust/create-profit': 1,
'future/trade/v1/entrust/update-profit-stop': 1,
'future/trade/v1/order/cancel': 1,
'future/trade/v1/order/cancel-all': 1,
'future/trade/v1/order/create': 1,
'future/trade/v1/order/create-batch': 1,
'future/trade/v1/order/update': 1,
'future/user/v1/account/open': 1,
'future/user/v1/position/adjust-leverage': 1,
'future/user/v1/position/auto-margin': 1,
'future/user/v1/position/close-all': 1,
'future/user/v1/position/margin': 1,
'future/user/v1/user/collection/add': 1,
'future/user/v1/user/collection/cancel': 1,
},
},
'user': {
'get': {
'user/account': 1,
'user/account/api-key': 1,
},
'post': {
'user/account': 1,
'user/account/api-key': 1,
},
'put': {
'user/account/api-key': 1,
},
'delete': {
'user/account/{apiKeyId}': 1,
},
},
},
},
'fees': {
'spot': {
'tierBased': True,
'percentage': True,
'maker': self.parse_number('0.002'),
'taker': self.parse_number('0.002'),
'tiers': {
'maker': [
[self.parse_number('0'), self.parse_number('0.002')],
[self.parse_number('5000'), self.parse_number('0.0018')],
[self.parse_number('10000'), self.parse_number('0.0016')],
[self.parse_number('20000'), self.parse_number('0.0014')],
[self.parse_number('50000'), self.parse_number('0.0012')],
[self.parse_number('150000'), self.parse_number('0.0010')],
[self.parse_number('300000'), self.parse_number('0.0008')],
[self.parse_number('600000'), self.parse_number('0.0007')],
[self.parse_number('1200000'), self.parse_number('0.0006')],
[self.parse_number('2500000'), self.parse_number('0.0005')],
[self.parse_number('6000000'), self.parse_number('0.0004')],
[self.parse_number('15000000'), self.parse_number('0.0003')],
[self.parse_number('30000000'), self.parse_number('0.0002')],
],
'taker': [
[self.parse_number('0'), self.parse_number('0.002')],
[self.parse_number('5000'), self.parse_number('0.0018')],
[self.parse_number('10000'), self.parse_number('0.0016')],
[self.parse_number('20000'), self.parse_number('0.0014')],
[self.parse_number('50000'), self.parse_number('0.0012')],
[self.parse_number('150000'), self.parse_number('0.0010')],
[self.parse_number('300000'), self.parse_number('0.0008')],
[self.parse_number('600000'), self.parse_number('0.0007')],
[self.parse_number('1200000'), self.parse_number('0.0006')],
[self.parse_number('2500000'), self.parse_number('0.0005')],
[self.parse_number('6000000'), self.parse_number('0.0004')],
[self.parse_number('15000000'), self.parse_number('0.0003')],
[self.parse_number('30000000'), self.parse_number('0.0002')],
],
},
},
'contract': {
'tierBased': True,
'percentage': True,
'maker': self.parse_number('0.0004'),
'taker': self.parse_number('0.0006'),
'tiers': {
'maker': [
[self.parse_number('0'), self.parse_number('0.0004')],
[self.parse_number('200000'), self.parse_number('0.00038')],
[self.parse_number('1000000'), self.parse_number('0.00036')],
[self.parse_number('5000000'), self.parse_number('0.00034')],
[self.parse_number('10000000'), self.parse_number('0.00032')],
[self.parse_number('15000000'), self.parse_number('0.00028')],
[self.parse_number('30000000'), self.parse_number('0.00024')],
[self.parse_number('50000000'), self.parse_number('0.0002')],
[self.parse_number('100000000'), self.parse_number('0.00016')],
[self.parse_number('300000000'), self.parse_number('0.00012')],
[self.parse_number('500000000'), self.parse_number('0.00008')],
],
'taker': [
[self.parse_number('0'), self.parse_number('0.0006')],
[self.parse_number('200000'), self.parse_number('0.000588')],
[self.parse_number('1000000'), self.parse_number('0.00057')],
[self.parse_number('5000000'), self.parse_number('0.00054')],
[self.parse_number('10000000'), self.parse_number('0.00051')],
[self.parse_number('15000000'), self.parse_number('0.00048')],
[self.parse_number('30000000'), self.parse_number('0.00045')],
[self.parse_number('50000000'), self.parse_number('0.00045')],
[self.parse_number('100000000'), self.parse_number('0.00036')],
[self.parse_number('300000000'), self.parse_number('0.00033')],
[self.parse_number('500000000'), self.parse_number('0.0003')],
],
},
},
},
'exceptions': {
'exact': {
'400': NetworkError, # {"returnCode":1,"msgInfo":"failure","error":{"code":"400","msg":"Connection refused: /10.0.26.71:8080"},"result":null}
'404': ExchangeError, # interface does not exist
'429': RateLimitExceeded, # The request is too frequent, please control the request rate according to the speed limit requirement
'500': ExchangeError, # Service exception
'502': ExchangeError, # Gateway exception
'503': OnMaintenance, # Service unavailable, please try again later
'AUTH_001': AuthenticationError, # missing request header xt-validate-appkey
'AUTH_002': AuthenticationError, # missing request header xt-validate-timestamp
'AUTH_003': AuthenticationError, # missing request header xt-validate-recvwindow
'AUTH_004': AuthenticationError, # bad request header xt-validate-recvwindow
'AUTH_005': AuthenticationError, # missing request header xt-validate-algorithms
'AUTH_006': AuthenticationError, # bad request header xt-validate-algorithms
'AUTH_007': AuthenticationError, # missing request header xt-validate-signature
'AUTH_101': AuthenticationError, # ApiKey does not exist
'AUTH_102': AuthenticationError, # ApiKey is not activated
'AUTH_103': AuthenticationError, # Signature error, {"rc":1,"mc":"AUTH_103","ma":[],"result":null}
'AUTH_104': AuthenticationError, # Unbound IP request
'AUTH_105': AuthenticationError, # outdated message
'AUTH_106': PermissionDenied, # Exceeded apikey permission
'SYMBOL_001': BadSymbol, # Symbol not exist
'SYMBOL_002': BadSymbol, # Symbol offline
'SYMBOL_003': BadSymbol, # Symbol suspend trading
'SYMBOL_004': BadSymbol, # Symbol country disallow trading
'SYMBOL_005': BadSymbol, # The symbol does not support trading via API
'ORDER_001': InvalidOrder, # Platform rejection
'ORDER_002': InsufficientFunds, # insufficient funds
'ORDER_003': InvalidOrder, # Trading Pair Suspended
'ORDER_004': InvalidOrder, # no transaction
'ORDER_005': InvalidOrder, # Order not exist
'ORDER_006': InvalidOrder, # Too many open orders
'ORDER_007': PermissionDenied, # The sub-account has no transaction authority
'ORDER_F0101': InvalidOrder, # Trigger Price Filter - Min
'ORDER_F0102': InvalidOrder, # Trigger Price Filter - Max
'ORDER_F0103': InvalidOrder, # Trigger Price Filter - Step Value
'ORDER_F0201': InvalidOrder, # Trigger Quantity Filter - Min
'ORDER_F0202': InvalidOrder, # Trigger Quantity Filter - Max
'ORDER_F0203': InvalidOrder, # Trigger Quantity Filter - Step Value
'ORDER_F0301': InvalidOrder, # Trigger QUOTE_QTY Filter - Min Value
'ORDER_F0401': InvalidOrder, # Trigger PROTECTION_ONLINE Filter
'ORDER_F0501': InvalidOrder, # Trigger PROTECTION_LIMIT Filter - Buy Max Deviation
'ORDER_F0502': InvalidOrder, # Trigger PROTECTION_LIMIT Filter - Sell Max Deviation
'ORDER_F0601': InvalidOrder, # Trigger PROTECTION_MARKET Filter
'COMMON_001': ExchangeError, # The user does not exist
'COMMON_002': ExchangeError, # System busy, please try it later
'COMMON_003': BadRequest, # Operation failed, please try it later
'CURRENCY_001': BadRequest, # Information of currency is abnormal
'DEPOSIT_001': BadRequest, # Deposit is not open
'DEPOSIT_002': PermissionDenied, # The current account security level is low, please bind any two security verifications in mobile phone/email/Google Authenticator before deposit
'DEPOSIT_003': BadRequest, # The format of address is incorrect, please enter again
'DEPOSIT_004': BadRequest, # The address is already exists, please enter again
'DEPOSIT_005': BadRequest, # Can not find the address of offline wallet
'DEPOSIT_006': BadRequest, # No deposit address, please try it later
'DEPOSIT_007': BadRequest, # Address is being generated, please try it later
'DEPOSIT_008': BadRequest, # Deposit is not available
'WITHDRAW_001': BadRequest, # Withdraw is not open
'WITHDRAW_002': BadRequest, # The withdrawal address is invalid
'WITHDRAW_003': PermissionDenied, # The current account security level is low, please bind any two security verifications in mobile phone/email/Google Authenticator before withdraw
'WITHDRAW_004': BadRequest, # The withdrawal address is not added
'WITHDRAW_005': BadRequest, # The withdrawal address cannot be empty
'WITHDRAW_006': BadRequest, # Memo cannot be empty
'WITHDRAW_008': PermissionDenied, # Risk control is triggered, withdraw of self currency is not currently supported
'WITHDRAW_009': PermissionDenied, # Withdraw failed, some hasattr(self, assets) withdraw are restricted by T+1 withdraw
'WITHDRAW_010': BadRequest, # The precision of withdrawal is invalid
'WITHDRAW_011': InsufficientFunds, # free balance is not enough
'WITHDRAW_012': PermissionDenied, # Withdraw failed, your remaining withdrawal limit today is not enough
'WITHDRAW_013': PermissionDenied, # Withdraw failed, your remaining withdrawal limit today is not enough, the withdrawal amount can be increased by completing a higher level of real-name authentication
'WITHDRAW_014': BadRequest, # This withdrawal address cannot be used in the internal transfer function, please cancel the internal transfer function before submitting
'WITHDRAW_015': BadRequest, # The withdrawal amount is not enough to deduct the handling fee
'WITHDRAW_016': BadRequest, # This withdrawal address is already exists
'WITHDRAW_017': BadRequest, # This withdrawal has been processed and cannot be canceled
'WITHDRAW_018': BadRequest, # Memo must be a number
'WITHDRAW_019': BadRequest, # Memo is incorrect, please enter again
'WITHDRAW_020': PermissionDenied, # Your withdrawal amount has reached the upper limit for today, please try it tomorrow
'WITHDRAW_021': PermissionDenied, # Your withdrawal amount has reached the upper limit for today, you can only withdraw up to {0} self time
'WITHDRAW_022': BadRequest, # Withdrawal amount must be greater than {0}
'WITHDRAW_023': BadRequest, # Withdrawal amount must be less than {0}
'WITHDRAW_024': BadRequest, # Withdraw is not supported
'WITHDRAW_025': BadRequest, # Please create a FIO address in the deposit page
'FUND_001': BadRequest, # Duplicate request(a bizId can only be requested once)
'FUND_002': InsufficientFunds, # Insufficient account balance
'FUND_003': BadRequest, # Transfer operations are not supported(for example, sub-accounts do not support financial transfers)
'FUND_004': ExchangeError, # Unfreeze failed
'FUND_005': PermissionDenied, # Transfer prohibited
'FUND_014': BadRequest, # The transfer-in account id and transfer-out account ID cannot be the same
'FUND_015': BadRequest, # From and to business types cannot be the same
'FUND_016': BadRequest, # Leverage transfer, symbol cannot be empty
'FUND_017': BadRequest, # Parameter error
'FUND_018': BadRequest, # Invalid freeze record
'FUND_019': BadRequest, # Freeze users not equal
'FUND_020': BadRequest, # Freeze currency are not equal
'FUND_021': BadRequest, # Operation not supported
'FUND_022': BadRequest, # Freeze record does not exist
'FUND_044': BadRequest, # The maximum length of the amount is 113 and cannot exceed the limit
'TRANSFER_001': BadRequest, # Duplicate request(a bizId can only be requested once)
'TRANSFER_002': InsufficientFunds, # Insufficient account balance
'TRANSFER_003': BadRequest, # User not registered
'TRANSFER_004': PermissionDenied, # The currency is not allowed to be transferred
'TRANSFER_005': PermissionDenied, # The users currency is not allowed to be transferred
'TRANSFER_006': PermissionDenied, # Transfer prohibited
'TRANSFER_007': RequestTimeout, # Request timed out
'TRANSFER_008': BadRequest, # Transferring to a leveraged account is abnormal
'TRANSFER_009': BadRequest, # Departing from a leveraged account is abnormal
'TRANSFER_010': PermissionDenied, # Leverage cleared, transfer prohibited
'TRANSFER_011': PermissionDenied, # Leverage with borrowing, transfer prohibited
'TRANSFER_012': PermissionDenied, # Currency transfer prohibited
'symbol_not_support_trading_via_api': BadSymbol, # {"returnCode":1,"msgInfo":"failure","error":{"code":"symbol_not_support_trading_via_api","msg":"The symbol does not support trading via API"},"result":null}
'open_order_min_nominal_value_limit': InvalidOrder, # {"returnCode":1,"msgInfo":"failure","error":{"code":"open_order_min_nominal_value_limit","msg":"Exceeds the minimum notional value of a single order"},"result":null}
'insufficient_balance': InsufficientFunds,
},
'broad': {
'The symbol does not support trading via API': BadSymbol, # {"returnCode":1,"msgInfo":"failure","error":{"code":"symbol_not_support_trading_via_api","msg":"The symbol does not support trading via API"},"result":null}
'Exceeds the minimum notional value of a single order': InvalidOrder, # {"returnCode":1,"msgInfo":"failure","error":{"code":"open_order_min_nominal_value_limit","msg":"Exceeds the minimum notional value of a single order"},"result":null}
'insufficient balance': InsufficientFunds,
},
},
'timeframes': {
'1m': '1m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h', # spot only
'2h': '2h', # spot only
'4h': '4h',
'6h': '6h', # spot only
'8h': '8h', # spot only
'1d': '1d',
'3d': '3d', # spot only
'1w': '1w',
'1M': '1M', # spot only
},
'commonCurrencies': {},
'options': {
'adjustForTimeDifference': False,
'timeDifference': 0,
'accountsById': {
'spot': 'SPOT',
'leverage': 'LEVER',
'finance': 'FINANCE',
'swap': 'FUTURES_U',
'future': 'FUTURES_U',
'linear': 'FUTURES_U',
'inverse': 'FUTURES_C',
},
'networks': {
'ERC20': 'Ethereum',
'TRC20': 'Tron',
'BEP20': 'BNB Smart Chain',
'BEP2': 'BNB-BEP2',
'ETH': 'Ethereum',
'TRON': 'Tron',
'BNB': 'BNB Smart Chain',
'AVAX': 'AVAX C-Chain',
'GAL': 'GAL(FT)',
'ALEO': 'ALEO(IOU)',
'BTC': 'Bitcoin',
'XT': 'XT Smart Chain',
'ETC': 'Ethereum Classic',
'MATIC': 'Polygon',
'LTC': 'Litecoin',
'BTS': 'BitShares',
'XRP': 'Ripple',
'XLM': 'Stellar Network',
'ADA': 'Cardano',
'XWC': 'XWC-XWC',
'DOGE': 'dogecoin',
'DCR': 'Decred',
'SC': 'Siacoin',
'XTZ': 'Tezos',
'ZEC': 'Zcash',
'XMR': 'Monero',
'LSK': 'Lisk',
'ATOM': 'Cosmos',
'ONT': 'Ontology',
'ALGO': 'Algorand',
'SOL': 'SOL-SOL',
'DOT': 'Polkadot',
'ZEN': 'Horizen',
'FIL': 'Filecoin',
'CHZ': 'chz',
'ICP': 'Internet Computer',
'KSM': 'Kusama',
'LUNA': 'Terra',
'THETA': 'Theta Token',
'FTM': 'Fantom',
'VET': 'VeChain',
'NEAR': 'NEAR Protocol',
'ONE': 'Harmony',
'KLAY': 'Klaytn',
'AR': 'Arweave',
'CELT': 'OKT',
'EGLD': 'Elrond eGold',
'CRO': 'CRO-CRONOS',
'BCH': 'Bitcoin Cash',
'GLMR': 'Moonbeam',
'LOOP': 'LOOP-LRC',
'REI': 'REI Network',
'ASTR': 'Astar Network',
'OP': 'OPT',
'MMT': 'MMT-MMT',
'TBC': 'TBC-TBC',
'OMAX': 'OMAX-OMAX CHAIN',
'GMMT': 'GMMT chain',
'ZIL': 'Zilliqa',
},
'networksById': {
'Ethereum': 'ERC20',
'Tron': 'TRC20',
'BNB Smart Chain': 'BEP20',
'BNB-BEP2': 'BEP2',
'Bitcoin': 'BTC',
'XT Smart Chain': 'XT',
'Ethereum Classic': 'ETC',
'Polygon': 'MATIC',
'Litecoin': 'LTC',
'BitShares': 'BTS',
'Ripple': 'XRP',
'Stellar Network': 'XLM',
'Cardano': 'ADA',
'XWC-XWC': 'XWC',
'dogecoin': 'DOGE',
'Decred': 'DCR',
'Siacoin': 'SC',
'Tezos': 'XTZ',
'Zcash': 'ZEC',
'Monero': 'XMR',
'Lisk': 'LSK',
'Cosmos': 'ATOM',
'Ontology': 'ONT',
'Algorand': 'ALGO',
'SOL-SOL': 'SOL',
'Polkadot': 'DOT',
'Horizen': 'ZEN',
'Filecoin': 'FIL',
'chz': 'CHZ',
'Internet Computer': 'ICP',
'Kusama': 'KSM',
'Terra': 'LUNA',
'Theta Token': 'THETA',
'Fantom': 'FTM',
'VeChain': 'VET',
'AVAX C-Chain': 'AVAX',
'NEAR Protocol': 'NEAR',
'Harmony': 'ONE',
'Klaytn': 'KLAY',
'Arweave': 'AR',
'OKT': 'CELT',
'Elrond eGold': 'EGLD',
'CRO-CRONOS': 'CRO',
'Bitcoin Cash': 'BCH',
'Moonbeam': 'GLMR',
'LOOP-LRC': 'LOOP',
'REI Network': 'REI',
'Astar Network': 'ASTR',
'GAL(FT)': 'GAL',
'ALEO(IOU)': 'ALEO',
'OPT': 'OP',
'MMT-MMT': 'MMT',
'TBC-TBC': 'TBC',
'OMAX-OMAX CHAIN': 'OMAX',
'GMMT chain': 'GMMT',
'Zilliqa': 'ZIL',
},
'createMarketBuyOrderRequiresPrice': True,
'recvWindow': '5000', # in milliseconds, spot only
},
'features': {
'default': {
'sandbox': False,
'createOrder': {
'marginMode': False,
'triggerPrice': False,
'triggerDirection': False,
'triggerPriceType': None,
'stopLossPrice': False,
'takeProfitPrice': False,
'attachedStopLossTakeProfit': None,
'timeInForce': {
'IOC': True,
'FOK': True,
'PO': True,
'GTD': False,
},
'hedged': False,
'trailing': False,
'leverage': False,
'marketBuyByCost': True,
'marketBuyRequiresPrice': False,
'selfTradePrevention': False,
'iceberg': False,
},
'createOrders': None,
'fetchMyTrades': {
'marginMode': True,
'limit': 100,
'daysBack': 100000, # todo
'untilDays': 100000, # todo
'marketType': True,
'subType': True,
'symbolRequired': False,
},
'fetchOrder': {
'marginMode': False,
'trigger': True, # todo TPSL kind
'trailing': False,
'marketType': True,
'subType': True,
'symbolRequired': False,
},
'fetchOpenOrders': {
'marginMode': True,
'limit': 100,
'trigger': True, # todo TPSL
'trailing': False,
'marketType': True,
'subType': True,
'symbolRequired': False,
},
'fetchOrders': {
'marginMode': True,
'limit': 100,
'daysBack': 100000, # todo
'untilDays': 100000, # todo
'trigger': True, # todo TPSL
'trailing': False,
'marketType': True,
'subType': True,
'symbolRequired': False,
},
'fetchClosedOrders': {
'marginMode': True,
'limit': 100,
'daysBack': 100000, # todo
'daysBackCanceled': 1, # todo
'untilDays': 100000, # todo
'trigger': True, # todo TPSL
'trailing': False,
'marketType': True,
'subType': True,
'symbolRequired': False,
},
'fetchOHLCV': {
'limit': 1000, # todo for derivatives
},
},
'spot': {
'extends': 'default',
},
'forDerivatives': {
'extends': 'default',
'createOrder': {
'triggerPrice': True,
# todo
'triggerPriceType': {
'last': True,
'mark': True,
'index': True,
},
'stopLossPrice': True,
'takeProfitPrice': True,
},
'fetchMyTrades': {
'daysBack': None,
'untilDays': None,
},
},
'swap': {
'linear': {
'extends': 'forDerivatives',
},
'inverse': {
'extends': 'forDerivatives',
},
},
'future': {
'linear': {
'extends': 'forDerivatives',
},
'inverse': {
'extends': 'forDerivatives',
},
},
},
})
def nonce(self):
return self.milliseconds() - self.options['timeDifference']
def fetch_time(self, params={}) -> Int:
"""
fetches the current integer timestamp in milliseconds from the xt server
https://doc.xt.com/#market1serverInfo
:param dict params: extra parameters specific to the xt api endpoint
:returns int: the current integer timestamp in milliseconds from the xt server
"""
response = self.publicSpotGetTime(params)
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "serverTime": 1677823301643
# }
# }
#
data = self.safe_value(response, 'result')
return self.safe_integer(data, 'serverTime')
def fetch_currencies(self, params={}) -> Currencies:
"""
fetches all available currencies on an exchange
https://doc.xt.com/#deposit_withdrawalsupportedCurrenciesGet
:param dict params: extra parameters specific to the xt api endpoint
:returns dict: an associative dictionary of currencies
"""
promisesRaw = [self.publicSpotGetWalletSupportCurrency(params), self.publicSpotGetCurrencies(params)]
chainsResponse, currenciesResponse = promisesRaw
#
# currencies
#
# {
# "time": "1686626116145",
# "version": "5dbbb2f2527c22b2b2e3b47187ef13d1",
# "currencies": [
# {
# "id": "2",
# "currency": "btc",
# "fullName": "Bitcoin",
# "logo": "https://a.static-global.com/1/currency/btc.png",
# "cmcLink": "https://coinmarketcap.com/currencies/bitcoin/",
# "weight": "99999",
# "maxPrecision": "10",
# "depositStatus": "1",
# "withdrawStatus": "1",
# "convertEnabled": "1",
# "transferEnabled": "1",
# "isChainExist": "1",
# "plates": [152]
# },
# ],
# }
#
#
# chains
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": [
# {
# "currency": "btc",
# "supportChains": [
# {
# "chain": "Bitcoin",
# "depositEnabled": True,
# "withdrawEnabled": True,
# "withdrawFeeAmount": 0.0009,
# "withdrawMinAmount": 0.0005,
# "depositFeeRate": 0
# },
# ]
# },
# ]
# }
#
# note: individual network's full data is available on per-currency endpoint: https://www.xt.com/sapi/v4/balance/public/currency/11
#
chainsData = self.safe_value(chainsResponse, 'result', [])
currenciesResult = self.safe_value(currenciesResponse, 'result', [])
currenciesData = self.safe_value(currenciesResult, 'currencies', [])
chainsDataIndexed = self.index_by(chainsData, 'currency')
result = {}
for i in range(0, len(currenciesData)):
entry = currenciesData[i]
currencyId = self.safe_string(entry, 'currency')
code = self.safe_currency_code(currencyId)
networkEntry = self.safe_value(chainsDataIndexed, currencyId, {})
rawNetworks = self.safe_value(networkEntry, 'supportChains', [])
networks = {}
for j in range(0, len(rawNetworks)):
rawNetwork = rawNetworks[j]
networkId = self.safe_string(rawNetwork, 'chain')
networkCode = self.network_id_to_code(networkId, code)
networks[networkCode] = {
'info': rawNetwork,
'id': networkId,
'network': networkCode,
'name': None,
'active': None,
'fee': self.safe_number(rawNetwork, 'withdrawFeeAmount'),
'precision': None,
'deposit': self.safe_bool(rawNetwork, 'depositEnabled'),
'withdraw': self.safe_bool(rawNetwork, 'withdrawEnabled'),
'limits': {
'amount': {
'min': None,
'max': None,
},
'withdraw': {
'min': self.safe_number(rawNetwork, 'withdrawMinAmount'),
'max': None,
},
'deposit': {
'min': None,
'max': None,
},
},
}
typeRaw = self.safe_string(entry, 'type')
type: Str = None
if typeRaw == 'FT':
type = 'crypto'
else:
type = 'other'
result[code] = self.safe_currency_structure({
'info': entry,
'id': currencyId,
'code': code,
'name': self.safe_string(entry, 'fullName'),
'active': None,
'fee': None,
'precision': self.parse_number(self.parse_precision(self.safe_string(entry, 'maxPrecision'))),
'deposit': self.safe_string(entry, 'depositStatus') == '1',
'withdraw': self.safe_string(entry, 'withdrawStatus') == '1',
'networks': networks,
'type': type,
'limits': {
'amount': {
'min': None,
'max': None,
},
'withdraw': {
'min': None,
'max': None,
},
'deposit': {
'min': None,
'max': None,
},
},
})
return result
def fetch_markets(self, params={}) -> List[Market]:
"""
retrieves data on all markets for xt
https://doc.xt.com/#market2symbol
https://doc.xt.com/#futures_quotesgetSymbols
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: an array of objects representing market data
"""
if self.options['adjustForTimeDifference']:
self.load_time_difference()
promisesUnresolved = [
self.fetch_spot_markets(params),
self.fetch_swap_and_future_markets(params),
]
promises = promisesUnresolved
spotMarkets = promises[0]
swapAndFutureMarkets = promises[1]
return self.array_concat(spotMarkets, swapAndFutureMarkets)
def fetch_spot_markets(self, params={}):
response = self.publicSpotGetSymbol(params)
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "time": 1677881368812,
# "version": "abb101d1543e54bee40687b135411ba0",
# "symbols": [
# {
# "id": 640,
# "symbol": "xt_usdt",
# "state": "ONLINE",
# "stateTime": 1554048000000,
# "tradingEnabled": True,
# "openapiEnabled": True,
# "nextStateTime": null,
# "nextState": null,
# "depthMergePrecision": 5,
# "baseCurrency": "xt",
# "baseCurrencyPrecision": 8,
# "baseCurrencyId": 128,
# "quoteCurrency": "usdt",
# "quoteCurrencyPrecision": 8,
# "quoteCurrencyId": 11,
# "pricePrecision": 4,
# "quantityPrecision": 2,
# "orderTypes": ["LIMIT","MARKET"],
# "timeInForces": ["GTC","IOC"],
# "displayWeight": 10002,
# "displayLevel": "FULL",
# "plates": [],
# "filters":[
# {
# "filter": "QUOTE_QTY",
# "min": "1"
# },
# {
# "filter": "PROTECTION_LIMIT",
# "buyMaxDeviation": "0.8",
# "sellMaxDeviation": "4"
# },
# {
# "filter": "PROTECTION_MARKET",
# "maxDeviation": "0.02"
# }
# ]
# },
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
symbols = self.safe_value(data, 'symbols', [])
return self.parse_markets(symbols)
def fetch_swap_and_future_markets(self, params={}):
markets = [self.publicLinearGetFutureMarketV1PublicSymbolList(params), self.publicInverseGetFutureMarketV1PublicSymbolList(params)]
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "id": 52,
# "symbolGroupId": 71,
# "symbol": "xt_usdt",
# "pair": "xt_usdt",
# "contractType": "PERPETUAL",
# "productType": "perpetual",
# "predictEventType": null,
# "underlyingType": "U_BASED",
# "contractSize": "1",
# "tradeSwitch": True,
# "isDisplay": True,
# "isOpenApi": False,
# "state": 0,
# "initLeverage": 20,
# "initPositionType": "CROSSED",
# "baseCoin": "xt",
# "quoteCoin": "usdt",
# "baseCoinPrecision": 8,
# "baseCoinDisplayPrecision": 4,
# "quoteCoinPrecision": 8,
# "quoteCoinDisplayPrecision": 4,
# "quantityPrecision": 0,
# "pricePrecision": 4,
# "supportOrderType": "LIMIT,MARKET",
# "supportTimeInForce": "GTC,FOK,IOC,GTX",
# "supportEntrustType": "TAKE_PROFIT,STOP,TAKE_PROFIT_MARKET,STOP_MARKET,TRAILING_STOP_MARKET",
# "supportPositionType": "CROSSED,ISOLATED",
# "minQty": "1",
# "minNotional": "5",
# "maxNotional": "20000000",
# "multiplierDown": "0.1",
# "multiplierUp": "0.1",
# "maxOpenOrders": 200,
# "maxEntrusts": 200,
# "makerFee": "0.0004",
# "takerFee": "0.0006",
# "liquidationFee": "0.01",
# "marketTakeBound": "0.1",
# "depthPrecisionMerge": 5,
# "labels": ["HOT"],
# "onboardDate": 1657101601000,
# "enName": "XTUSDT ",
# "cnName": "XTUSDT",
# "minStepPrice": "0.0001",
# "minPrice": null,
# "maxPrice": null,
# "deliveryDate": 1669879634000,
# "deliveryPrice": null,
# "deliveryCompletion": False,
# "cnDesc": null,
# "enDesc": null
# },
# ]
# }
#
swapAndFutureMarkets = self.array_concat(self.safe_value(markets[0], 'result', []), self.safe_value(markets[1], 'result', []))
return self.parse_markets(swapAndFutureMarkets)
def parse_markets(self, markets):
result = []
for i in range(0, len(markets)):
result.append(self.parse_market(markets[i]))
return result
def parse_market(self, market: dict) -> Market:
#
# spot
#
# {
# "id": 640,
# "symbol": "xt_usdt",
# "state": "ONLINE",
# "stateTime": 1554048000000,
# "tradingEnabled": True,
# "openapiEnabled": True,
# "nextStateTime": null,
# "nextState": null,
# "depthMergePrecision": 5,
# "baseCurrency": "xt",
# "baseCurrencyPrecision": 8,
# "baseCurrencyId": 128,
# "quoteCurrency": "usdt",
# "quoteCurrencyPrecision": 8,
# "quoteCurrencyId": 11,
# "pricePrecision": 4,
# "quantityPrecision": 2,
# "orderTypes": ["LIMIT","MARKET"],
# "timeInForces": ["GTC","IOC"],
# "displayWeight": 10002,
# "displayLevel": "FULL",
# "plates": [],
# "filters":[
# {
# "filter": "QUOTE_QTY",
# "min": "1"
# },
# {
# "filter": "PRICE",
# "min": null,
# "max": null,
# "tickSize": null
# },
# {
# "filter": "QUANTITY",
# "min": null,
# "max": null,
# "tickSize": null
# },
# {
# "filter": "PROTECTION_LIMIT",
# "buyMaxDeviation": "0.8",
# "sellMaxDeviation": "4"
# },
# {
# "filter": "PROTECTION_MARKET",
# "maxDeviation": "0.02"
# },
# {
# "filter": "PROTECTION_ONLINE",
# "durationSeconds": "300",
# "maxPriceMultiple": "5"
# },
# ]
# }
#
# swap and future
#
# {
# "id": 52,
# "symbolGroupId": 71,
# "symbol": "xt_usdt",
# "pair": "xt_usdt",
# "contractType": "PERPETUAL",
# "productType": "perpetual",
# "predictEventType": null,
# "underlyingType": "U_BASED",
# "contractSize": "1",
# "tradeSwitch": True,
# "isDisplay": True,
# "isOpenApi": False,
# "state": 0,
# "initLeverage": 20,
# "initPositionType": "CROSSED",
# "baseCoin": "xt",
# "quoteCoin": "usdt",
# "baseCoinPrecision": 8,
# "baseCoinDisplayPrecision": 4,
# "quoteCoinPrecision": 8,
# "quoteCoinDisplayPrecision": 4,
# "quantityPrecision": 0,
# "pricePrecision": 4,
# "supportOrderType": "LIMIT,MARKET",
# "supportTimeInForce": "GTC,FOK,IOC,GTX",
# "supportEntrustType": "TAKE_PROFIT,STOP,TAKE_PROFIT_MARKET,STOP_MARKET,TRAILING_STOP_MARKET",
# "supportPositionType": "CROSSED,ISOLATED",
# "minQty": "1",
# "minNotional": "5",
# "maxNotional": "20000000",
# "multiplierDown": "0.1",
# "multiplierUp": "0.1",
# "maxOpenOrders": 200,
# "maxEntrusts": 200,
# "makerFee": "0.0004",
# "takerFee": "0.0006",
# "liquidationFee": "0.01",
# "marketTakeBound": "0.1",
# "depthPrecisionMerge": 5,
# "labels": ["HOT"],
# "onboardDate": 1657101601000,
# "enName": "XTUSDT ",
# "cnName": "XTUSDT",
# "minStepPrice": "0.0001",
# "minPrice": null,
# "maxPrice": null,
# "deliveryDate": 1669879634000,
# "deliveryPrice": null,
# "deliveryCompletion": False,
# "cnDesc": null,
# "enDesc": null
# }
#
id = self.safe_string(market, 'symbol')
baseId = self.safe_string_2(market, 'baseCurrency', 'baseCoin')
quoteId = self.safe_string_2(market, 'quoteCurrency', 'quoteCoin')
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
state = self.safe_string(market, 'state')
symbol = base + '/' + quote
filters = self.safe_value(market, 'filters', [])
minAmount = None
maxAmount = None
minCost = None
maxCost = None
minPrice = None
maxPrice = None
amountPrecision = None
for i in range(0, len(filters)):
entry = filters[i]
filter = self.safe_string(entry, 'filter')
if filter == 'QUANTITY':
minAmount = self.safe_number(entry, 'min')
maxAmount = self.safe_number(entry, 'max')
amountPrecision = self.safe_number(entry, 'tickSize')
if filter == 'QUOTE_QTY':
minCost = self.safe_number(entry, 'min')
if filter == 'PRICE':
minPrice = self.safe_number(entry, 'min')
maxPrice = self.safe_number(entry, 'max')
if amountPrecision is None:
amountPrecision = self.parse_number(self.parse_precision(self.safe_string(market, 'quantityPrecision')))
underlyingType = self.safe_string(market, 'underlyingType')
linear = None
inverse = None
settleId = None
settle = None
expiry = None
future = False
swap = False
contract = False
spot = True
type = 'spot'
if underlyingType == 'U_BASED':
symbol = symbol + ':' + quote
settleId = baseId
settle = quote
linear = True
inverse = False
elif underlyingType == 'COIN_BASED':
symbol = symbol + ':' + base
settleId = baseId
settle = base
linear = False
inverse = True
if underlyingType is not None:
expiry = self.safe_integer(market, 'deliveryDate')
productType = self.safe_string(market, 'productType')
if productType != 'perpetual':
symbol = symbol + '-' + self.yymmdd(expiry)
type = 'future'
future = True
else:
type = 'swap'
swap = True
minAmount = self.safe_number(market, 'minQty')
minCost = self.safe_number(market, 'minNotional')
maxCost = self.safe_number(market, 'maxNotional')
minPrice = self.safe_number(market, 'minPrice')
maxPrice = self.safe_number(market, 'maxPrice')
contract = True
spot = False
isActive = False
if contract:
isActive = self.safe_value(market, 'isOpenApi', False)
else:
if (state == 'ONLINE') and (self.safe_value(market, 'tradingEnabled')) and (self.safe_value(market, 'openapiEnabled')):
isActive = True
return self.safe_market_structure({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': settleId,
'type': type,
'spot': spot,
'margin': None,
'swap': swap,
'future': future,
'option': False,
'active': isActive,
'contract': contract,
'linear': linear,
'inverse': inverse,
'taker': self.safe_number(market, 'takerFee'),
'maker': self.safe_number(market, 'makerFee'),
'contractSize': self.safe_number(market, 'contractSize'),
'expiry': expiry,
'expiryDatetime': self.iso8601(expiry),
'strike': None,
'optionType': None,
'precision': {
'price': self.parse_number(self.parse_precision(self.safe_string(market, 'pricePrecision'))),
'amount': amountPrecision,
'base': self.parse_number(self.parse_precision(self.safe_string(market, 'baseCoinPrecision'))),
'quote': self.parse_number(self.parse_precision(self.safe_string(market, 'quoteCoinPrecision'))),
},
'limits': {
'leverage': {
'min': self.parse_number('1'),
'max': None,
},
'amount': {
'min': minAmount,
'max': maxAmount,
},
'price': {
'min': minPrice,
'max': maxPrice,
},
'cost': {
'min': minCost,
'max': maxCost,
},
},
'info': market,
})
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}):
"""
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://doc.xt.com/#market4kline
https://doc.xt.com/#futures_quotesgetKLine
: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 xt api endpoint
:param int [params.until]: timestamp in ms of the latest candle to fetch
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
:returns int[][]: A list of candles ordered, open, high, low, close, volume
"""
self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False)
if paginate:
return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 1000)
market = self.market(symbol)
request = {
'symbol': market['id'],
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
}
if since is not None:
request['startTime'] = since
if limit is not None:
request['limit'] = limit
else:
request['limit'] = 1000
until = self.safe_integer(params, 'until')
params = self.omit(params, ['until'])
if until is not None:
request['endTime'] = until
response = None
if market['linear']:
response = self.publicLinearGetFutureMarketV1PublicQKline(self.extend(request, params))
elif market['inverse']:
response = self.publicInverseGetFutureMarketV1PublicQKline(self.extend(request, params))
else:
response = self.publicSpotGetKline(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": [
# {
# "t": 1678167720000,
# "o": "22467.85",
# "c": "22465.87",
# "h": "22468.86",
# "l": "22465.21",
# "q": "1.316656",
# "v": "29582.73018498"
# },
# ]
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "s": "btc_usdt",
# "p": "btc_usdt",
# "t": 1678168020000,
# "o": "22450.0",
# "c": "22441.5",
# "h": "22450.0",
# "l": "22441.5",
# "a": "312931",
# "v": "702461.58895"
# },
# ]
# }
#
ohlcvs = self.safe_value(response, 'result', [])
return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
#
# spot
#
# {
# "t": 1678167720000,
# "o": "22467.85",
# "c": "22465.87",
# "h": "22468.86",
# "l": "22465.21",
# "q": "1.316656",
# "v": "29582.73018498"
# }
#
# swap and future
#
# {
# "s": "btc_usdt",
# "p": "btc_usdt",
# "t": 1678168020000,
# "o": "22450.0",
# "c": "22441.5",
# "h": "22450.0",
# "l": "22441.5",
# "a": "312931",
# "v": "702461.58895"
# }
#
volumeIndex = 'v' if (market['inverse']) else 'a'
return [
self.safe_integer(ohlcv, 't'),
self.safe_number(ohlcv, 'o'),
self.safe_number(ohlcv, 'h'),
self.safe_number(ohlcv, 'l'),
self.safe_number(ohlcv, 'c'),
self.safe_number_2(ohlcv, 'q', volumeIndex),
]
def fetch_order_book(self, symbol: str, limit: Int = None, params={}):
"""
https://doc.xt.com/#market3depth
https://doc.xt.com/#futures_quotesgetDepth
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
:param str symbol: unified market symbol 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 xt api endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
"""
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
}
response = None
if market['spot']:
if limit is not None:
request['limit'] = min(limit, 500)
response = self.publicSpotGetDepth(self.extend(request, params))
else:
if limit is not None:
request['level'] = min(limit, 50)
else:
request['level'] = 50
if market['linear']:
response = self.publicLinearGetFutureMarketV1PublicQDepth(self.extend(request, params))
elif market['inverse']:
response = self.publicInverseGetFutureMarketV1PublicQDepth(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "timestamp": 1678169975184,
# "lastUpdateId": 1675333221812,
# "bids": [
# ["22444.51", "0.129887"],
# ["22444.49", "0.114245"],
# ["22444.30", "0.225956"]
# ],
# "asks": [
# ["22446.19", "0.095330"],
# ["22446.24", "0.224413"],
# ["22446.28", "0.329095"]
# ]
# }
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "t": 1678170311005,
# "s": "btc_usdt",
# "u": 471694545627,
# "b": [
# ["22426", "198623"],
# ["22423.5", "80295"],
# ["22423", "163580"]
# ],
# "a": [
# ["22427", "3417"],
# ["22428.5", "43532"],
# ["22429", "119"]
# ]
# }
# }
#
orderBook = self.safe_value(response, 'result', {})
timestamp = self.safe_integer_2(orderBook, 'timestamp', 't')
if market['spot']:
ob = self.parse_order_book(orderBook, symbol, timestamp)
ob['nonce'] = self.safe_integer(orderBook, 'lastUpdateId')
return ob
swapOb = self.parse_order_book(orderBook, symbol, timestamp, 'b', 'a')
swapOb['nonce'] = self.safe_integer_2(orderBook, 'u', 'lastUpdateId')
return swapOb
def fetch_ticker(self, symbol: str, params={}):
"""
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://doc.xt.com/#market10ticker24h
https://doc.xt.com/#futures_quotesgetAggTicker
:param str symbol: unified market symbol to fetch the ticker for
:param dict params: extra parameters specific to the xt api endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
"""
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
}
response = None
if market['linear']:
response = self.publicLinearGetFutureMarketV1PublicQAggTicker(self.extend(request, params))
elif market['inverse']:
response = self.publicInverseGetFutureMarketV1PublicQAggTicker(self.extend(request, params))
else:
response = self.publicSpotGetTicker24h(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": [
# {
# "s": "btc_usdt",
# "t": 1678172693931,
# "cv": "34.00",
# "cr": "0.0015",
# "o": "22398.05",
# "l": "22323.72",
# "h": "22600.50",
# "c": "22432.05",
# "q": "7962.256931",
# "v": "178675209.47416856"
# }
# ]
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "t": 1678172848572,
# "s": "btc_usdt",
# "c": "22415.5",
# "h": "22590.0",
# "l": "22310.0",
# "a": "623654031",
# "v": "1399166074.31675",
# "o": "22381.5",
# "r": "0.0015",
# "i": "22424.5",
# "m": "22416.5",
# "bp": "22415",
# "ap": "22415.5"
# }
# }
#
ticker = self.safe_value(response, 'result')
if market['spot']:
return self.parse_ticker(ticker[0], market)
return self.parse_ticker(ticker, market)
def fetch_tickers(self, symbols: List[str] = None, params={}) -> Tickers:
"""
fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
https://doc.xt.com/#market10ticker24h
https://doc.xt.com/#futures_quotesgetAggTickers
: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 xt api endpoint
:returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
"""
self.load_markets()
market = None
if symbols is not None:
symbols = self.market_symbols(symbols)
market = self.market(symbols[0])
request = {}
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('fetchTickers', market, params)
subType, params = self.handle_sub_type_and_params('fetchTickers', market, params)
if subType == 'inverse':
response = self.publicInverseGetFutureMarketV1PublicQAggTickers(self.extend(request, params))
elif (subType == 'linear') or (type == 'swap') or (type == 'future'):
response = self.publicLinearGetFutureMarketV1PublicQAggTickers(self.extend(request, params))
else:
response = self.publicSpotGetTicker24h(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": [
# {
# "s": "btc_usdt",
# "t": 1678172693931,
# "cv": "34.00",
# "cr": "0.0015",
# "o": "22398.05",
# "l": "22323.72",
# "h": "22600.50",
# "c": "22432.05",
# "q": "7962.256931",
# "v": "178675209.47416856"
# }
# ]
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "t": 1680738775108,
# "s": "badger_usdt",
# "c": "2.7176",
# "h": "2.7917",
# "l": "2.6818",
# "a": "88332",
# "v": "242286.3520",
# "o": "2.7422",
# "r": "-0.0089",
# "i": "2.7155",
# "m": "2.7161",
# "bp": "2.7152",
# "ap": "2.7176"
# },
# ]
# }
#
tickers = self.safe_value(response, 'result', [])
result = {}
for i in range(0, len(tickers)):
ticker = self.parse_ticker(tickers[i], market)
symbol = ticker['symbol']
result[symbol] = ticker
return self.filter_by_array(result, 'symbol', symbols)
def fetch_bids_asks(self, symbols: List[str] = None, params={}):
"""
fetches the bid and ask price and volume for multiple markets
https://doc.xt.com/#market9tickerBook
:param str [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 xt api endpoint
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
"""
self.load_markets()
symbols = self.market_symbols(symbols)
request = {}
market = None
if symbols is not None:
market = self.market(symbols[0])
subType = None
subType, params = self.handle_sub_type_and_params('fetchBidsAsks', market, params)
if subType is not None:
raise NotSupported(self.id + ' fetchBidsAsks() is not available for swap and future markets, only spot markets are supported')
response = self.publicSpotGetTickerBook(self.extend(request, params))
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": [
# {
# "s": "kas_usdt",
# "t": 1679539891853,
# "ap": "0.016298",
# "aq": "5119.09",
# "bp": "0.016290",
# "bq": "135.37"
# },
# ]
# }
#
tickers = self.safe_value(response, 'result', [])
return self.parse_tickers(tickers, symbols)
def parse_ticker(self, ticker, market=None):
#
# spot: fetchTicker, fetchTickers
#
# {
# "s": "btc_usdt",
# "t": 1678172693931,
# "cv": "34.00",
# "cr": "0.0015",
# "o": "22398.05",
# "l": "22323.72",
# "h": "22600.50",
# "c": "22432.05",
# "q": "7962.256931",
# "v": "178675209.47416856"
# }
#
# swap and future: fetchTicker, fetchTickers
#
# {
# "t": 1678172848572,
# "s": "btc_usdt",
# "c": "22415.5",
# "h": "22590.0",
# "l": "22310.0",
# "a": "623654031",
# "v": "1399166074.31675",
# "o": "22381.5",
# "r": "0.0015",
# "i": "22424.5",
# "m": "22416.5",
# "bp": "22415",
# "ap": "22415.5"
# }
#
# fetchBidsAsks
#
# {
# "s": "kas_usdt",
# "t": 1679539891853,
# "ap": "0.016298",
# "aq": "5119.09",
# "bp": "0.016290",
# "bq": "135.37"
# }
#
marketId = self.safe_string(ticker, 's')
marketType = market['type'] if (market is not None) else None
hasSpotKeys = ('cv' in ticker) or ('aq' in ticker)
if marketType is None:
marketType = 'spot' if hasSpotKeys else 'contract'
market = self.safe_market(marketId, market, '_', marketType)
symbol = market['symbol']
timestamp = self.safe_integer(ticker, 't')
percentage = self.safe_string_2(ticker, 'cr', 'r')
if percentage is not None:
percentage = Precise.string_mul(percentage, '100')
return self.safe_ticker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'high': self.safe_number(ticker, 'h'),
'low': self.safe_number(ticker, 'l'),
'bid': self.safe_number(ticker, 'bp'),
'bidVolume': self.safe_number(ticker, 'bq'),
'ask': self.safe_number(ticker, 'ap'),
'askVolume': self.safe_number(ticker, 'aq'),
'vwap': None,
'open': self.safe_string(ticker, 'o'),
'close': self.safe_string(ticker, 'c'),
'last': self.safe_string(ticker, 'c'),
'previousClose': None,
'change': self.safe_number(ticker, 'cv'),
'percentage': self.parse_number(percentage),
'average': None,
'baseVolume': None,
'quoteVolume': self.safe_number_2(ticker, 'a', 'v'),
'info': ticker,
}, market)
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}):
"""
get the list of most recent trades for a particular symbol
https://doc.xt.com/#market5tradeRecent
https://doc.xt.com/#futures_quotesgetDeal
:param str symbol: unified market symbol to fetch trades for
:param int [since]: timestamp in ms of the earliest trade to fetch
:param int [limit]: the maximum amount of trades to fetch
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
"""
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
}
response = None
if market['spot']:
if limit is not None:
request['limit'] = limit
response = self.publicSpotGetTradeRecent(self.extend(request, params))
else:
if limit is not None:
request['num'] = limit
if market['linear']:
response = self.publicLinearGetFutureMarketV1PublicQDeal(self.extend(request, params))
elif market['inverse']:
response = self.publicInverseGetFutureMarketV1PublicQDeal(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": [
# {
# "i": 203530723141917063,
# "t": 1678227505815,
# "p": "22038.81",
# "q": "0.000978",
# "v": "21.55395618",
# "b": True
# },
# ]
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "t": 1678227683897,
# "s": "btc_usdt",
# "p": "22031",
# "a": "1067",
# "m": "BID"
# },
# ]
# }
#
trades = self.safe_value(response, 'result', [])
return self.parse_trades(trades, market)
def fetch_my_trades(self, symbol: str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch all trades made by the user
https://doc.xt.com/#tradetradeGet
https://doc.xt.com/#futures_ordergetTrades
:param str [symbol]: unified market symbol to fetch trades for
:param int [since]: timestamp in ms of the earliest trade to fetch
:param int [limit]: the maximum amount of trades to fetch
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
"""
self.load_markets()
request = {}
market = None
if symbol is not None:
market = self.market(symbol)
request['symbol'] = market['id']
if since is not None:
request['startTime'] = since
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('fetchMyTrades', market, params)
subType, params = self.handle_sub_type_and_params('fetchMyTrades', market, params)
if (subType is not None) or (type == 'swap') or (type == 'future'):
if limit is not None:
request['size'] = limit
if subType == 'inverse':
response = self.privateInverseGetFutureTradeV1OrderTradeList(self.extend(request, params))
else:
response = self.privateLinearGetFutureTradeV1OrderTradeList(self.extend(request, params))
else:
marginMode = None
marginMode, params = self.handle_margin_mode_and_params('fetchMyTrades', params)
marginOrSpotRequest = 'LEVER' if (marginMode is not None) else 'SPOT'
request['bizType'] = marginOrSpotRequest
if limit is not None:
request['limit'] = limit
response = self.privateSpotGetTrade(self.extend(request, params))
#
# spot and margin
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "hasPrev": False,
# "hasNext": False,
# "items": [
# {
# "symbol": "btc_usdt",
# "tradeId": "206906233569974658",
# "orderId": "206906233178463488",
# "orderSide": "SELL",
# "orderType": "MARKET",
# "bizType": "SPOT",
# "time": 1679032290215,
# "price": "25703.46",
# "quantity": "0.000099",
# "quoteQty": "2.54464254",
# "baseCurrency": "btc",
# "quoteCurrency": "usdt",
# "fee": "0.00508929",
# "feeCurrency": "usdt",
# "takerMaker": "TAKER"
# },
# ]
# }
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "page": 1,
# "ps": 10,
# "total": 2,
# "items": [
# {
# "orderId": "207260566170987200",
# "execId": "207260566790603265",
# "symbol": "btc_usdt",
# "quantity": "13",
# "price": "27368",
# "fee": "0.02134704",
# "feeCoin": "usdt",
# "timestamp": 1679116769838,
# "takerMaker": "TAKER"
# },
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
trades = self.safe_value(data, 'items', [])
return self.parse_trades(trades, market, since, limit)
def parse_trade(self, trade, market=None):
#
# spot: fetchTrades
#
# {
# "i": 203530723141917063,
# "t": 1678227505815,
# "p": "22038.81",
# "q": "0.000978",
# "v": "21.55395618",
# "b": True
# }
#
# spot: watchTrades
#
# {
# s: 'btc_usdt',
# i: '228825383103928709',
# t: 1684258222702,
# p: '27003.65',
# q: '0.000796',
# b: True
# }
#
# spot: watchMyTrades
#
# {
# "s": "btc_usdt", # symbol
# "t": 1656043204763, # time
# "i": "6316559590087251233", # tradeId
# "oi": "6216559590087220004", # orderId
# "p": "30000", # trade price
# "q": "3", # qty quantity
# "v": "90000" # volume trade amount
# }
#
# swap and future: fetchTrades
#
# {
# "t": 1678227683897,
# "s": "btc_usdt",
# "p": "22031",
# "a": "1067",
# "m": "BID"
# }
#
# spot: fetchMyTrades
#
# {
# "symbol": "btc_usdt",
# "tradeId": "206906233569974658",
# "orderId": "206906233178463488",
# "orderSide": "SELL",
# "orderType": "MARKET",
# "bizType": "SPOT",
# "time": 1679032290215,
# "price": "25703.46",
# "quantity": "0.000099",
# "quoteQty": "2.54464254",
# "baseCurrency": "btc",
# "quoteCurrency": "usdt",
# "fee": "0.00508929",
# "feeCurrency": "usdt",
# "takerMaker": "TAKER"
# }
#
# swap and future: fetchMyTrades
#
# {
# "orderId": "207260566170987200",
# "execId": "207260566790603265",
# "symbol": "btc_usdt",
# "quantity": "13",
# "price": "27368",
# "fee": "0.02134704",
# "feeCoin": "usdt",
# "timestamp": 1679116769838,
# "takerMaker": "TAKER"
# }
#
# contract watchMyTrades
#
# {
# "symbol": 'btc_usdt',
# "orderSide": 'SELL',
# "positionSide": 'LONG',
# "orderId": '231485367663419328',
# "price": '27152.7',
# "quantity": '33',
# "marginUnfrozen": '2.85318000',
# "timestamp": 1684892412565
# }
#
# watchMyTrades(ws, swap)
#
# {
# 'fee': '0.04080840',
# 'isMaker': False,
# 'marginUnfrozen': '0.75711984',
# 'orderId': '376172779053188416',
# 'orderSide': 'BUY',
# 'positionSide': 'LONG',
# 'price': '3400.70',
# 'quantity': '2',
# 'symbol': 'eth_usdt',
# 'timestamp': 1719388579622
# }
#
marketId = self.safe_string_2(trade, 's', 'symbol')
marketType = market['type'] if (market is not None) else None
hasSpotKeys = ('b' in trade) or ('bizType' in trade) or ('oi' in trade)
if marketType is None:
marketType = 'spot' if hasSpotKeys else 'contract'
market = self.safe_market(marketId, market, '_', marketType)
side = None
takerOrMaker = None
isBuyerMaker = self.safe_bool(trade, 'b')
if isBuyerMaker is not None:
side = 'sell' if isBuyerMaker else 'buy'
takerOrMaker = 'taker' # public trades always taker
else:
takerMaker = self.safe_string_lower(trade, 'takerMaker')
if takerMaker is not None:
takerOrMaker = takerMaker
else:
isMaker = self.safe_bool(trade, 'isMaker')
if isMaker is not None:
takerOrMaker = 'maker' if isMaker else 'taker'
orderSide = self.safe_string_lower(trade, 'orderSide')
if orderSide is not None:
side = orderSide
else:
bidOrAsk = self.safe_string(trade, 'm')
if bidOrAsk is not None:
side = 'buy' if (bidOrAsk == 'BID') else 'sell'
timestamp = self.safe_integer_n(trade, ['t', 'time', 'timestamp'])
quantity = self.safe_string_2(trade, 'q', 'quantity')
amount = None
if marketType == 'spot':
amount = quantity
else:
if quantity is None:
amount = Precise.string_mul(self.safe_string(trade, 'a'), self.number_to_string(market['contractSize']))
else:
amount = Precise.string_mul(quantity, self.number_to_string(market['contractSize']))
return self.safe_trade({
'info': trade,
'id': self.safe_string_n(trade, ['i', 'tradeId', 'execId']),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': market['symbol'],
'order': self.safe_string_2(trade, 'orderId', 'oi'),
'type': self.safe_string_lower(trade, 'orderType'),
'side': side,
'takerOrMaker': takerOrMaker,
'price': self.safe_string_2(trade, 'p', 'price'),
'amount': amount,
'cost': None,
'fee': {
'currency': self.safe_currency_code(self.safe_string_2(trade, 'feeCurrency', 'feeCoin')),
'cost': self.safe_string(trade, 'fee'),
},
}, market)
def fetch_balance(self, params={}):
"""
query for balance and get the amount of funds available for trading or funds locked in orders
https://doc.xt.com/#balancebalancesGet
https://doc.xt.com/#futures_usergetBalances
:param dict params: extra parameters specific to the xt api endpoint
:returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
"""
self.load_markets()
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('fetchBalance', None, params)
subType, params = self.handle_sub_type_and_params('fetchBalance', None, params)
isContractWallet = ((type == 'swap') or (type == 'future'))
if subType == 'inverse':
response = self.privateInverseGetFutureUserV1BalanceList(params)
elif (subType == 'linear') or isContractWallet:
response = self.privateLinearGetFutureUserV1BalanceList(params)
else:
response = self.privateSpotGetBalances(params)
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "totalUsdtAmount": "31.75931133",
# "totalBtcAmount": "0.00115951",
# "assets": [
# {
# "currency": "usdt",
# "currencyId": 11,
# "frozenAmount": "0.03834082",
# "availableAmount": "31.70995965",
# "totalAmount": "31.74830047",
# "convertBtcAmount": "0.00115911",
# "convertUsdtAmount": "31.74830047"
# },
# ]
# }
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "coin": "usdt",
# "walletBalance": "19.29849875",
# "openOrderMarginFrozen": "0",
# "isolatedMargin": "0.709475",
# "crossedMargin": "0",
# "availableBalance": "18.58902375",
# "bonus": "0",
# "coupon":"0"
# }
# ]
# }
#
balances = None
if (subType is not None) or isContractWallet:
balances = self.safe_value(response, 'result', [])
else:
data = self.safe_value(response, 'result', {})
balances = self.safe_value(data, 'assets', [])
return self.parse_balance(balances)
def parse_balance(self, response):
#
# spot
#
# {
# "currency": "usdt",
# "currencyId": 11,
# "frozenAmount": "0.03834082",
# "availableAmount": "31.70995965",
# "totalAmount": "31.74830047",
# "convertBtcAmount": "0.00115911",
# "convertUsdtAmount": "31.74830047"
# }
#
# swap and future
#
# {
# "coin": "usdt",
# "walletBalance": "19.29849875",
# "openOrderMarginFrozen": "0",
# "isolatedMargin": "0.709475",
# "crossedMargin": "0",
# "availableBalance": "18.58902375",
# "bonus": "0",
# "coupon":"0"
# }
#
result = {'info': response}
for i in range(0, len(response)):
balance = response[i]
currencyId = self.safe_string_2(balance, 'currency', 'coin')
code = self.safe_currency_code(currencyId)
account = self.account()
free = self.safe_string_2(balance, 'availableAmount', 'availableBalance')
used = self.safe_string(balance, 'frozenAmount')
total = self.safe_string_2(balance, 'totalAmount', 'walletBalance')
if used is None:
crossedAndIsolatedMargin = Precise.string_add(self.safe_string(balance, 'crossedMargin'), self.safe_string(balance, 'isolatedMargin'))
used = Precise.string_add(self.safe_string(balance, 'openOrderMarginFrozen'), crossedAndIsolatedMargin)
account['free'] = free
account['used'] = used
account['total'] = total
result[code] = account
return self.safe_balance(result)
def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
"""
https://doc.xt.com/#orderorderPost
create a market buy order by providing the symbol and cost
:param str symbol: unified symbol of the market to create an order in
:param float cost: how much you want to trade in units of the quote currency
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
self.load_markets()
market = self.market(symbol)
if not market['spot']:
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports spot orders only')
return self.create_order(symbol, 'market', 'buy', cost, 1, params)
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
create a trade order
https://doc.xt.com/#orderorderPost
https://doc.xt.com/#futures_ordercreate
https://doc.xt.com/#futures_entrustcreatePlan
https://doc.xt.com/#futures_entrustcreateProfit
: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 to fulfill the order, in units of the quote currency, can be ignored in market orders
:param dict params: extra parameters specific to the xt api endpoint
:param str [params.timeInForce]: 'GTC', 'IOC', 'FOK' or 'GTX'
:param str [params.entrustType]: 'TAKE_PROFIT', 'STOP', 'TAKE_PROFIT_MARKET', 'STOP_MARKET', 'TRAILING_STOP_MARKET', required if stopPrice is defined, currently isn't functioning on xt's side
:param str [params.triggerPriceType]: 'INDEX_PRICE', 'MARK_PRICE', 'LATEST_PRICE', required if stopPrice is defined
:param float [params.triggerPrice]: price to trigger a stop order
:param float [params.stopPrice]: alias for triggerPrice
:param float [params.stopLoss]: price to set a stop-loss on an open position
:param float [params.takeProfit]: price to set a take-profit on an open position
:returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
self.load_markets()
market = self.market(symbol)
symbol = market['symbol']
if market['spot']:
return self.create_spot_order(symbol, type, side, amount, price, params)
else:
return self.create_contract_order(symbol, type, side, amount, price, params)
def create_spot_order(self, symbol: str, type, side, amount, price=None, params={}):
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
'side': side.upper(),
'type': type.upper(),
}
timeInForce = None
marginMode = None
marginMode, params = self.handle_margin_mode_and_params('createOrder', params)
marginOrSpotRequest = 'LEVER' if (marginMode is not None) else 'SPOT'
request['bizType'] = marginOrSpotRequest
if type == 'market':
timeInForce = self.safe_string_upper(params, 'timeInForce', 'FOK')
if side == 'buy':
cost = self.safe_string(params, 'cost')
params = self.omit(params, 'cost')
createMarketBuyOrderRequiresPrice = self.safe_bool(self.options, 'createMarketBuyOrderRequiresPrice', True)
if createMarketBuyOrderRequiresPrice:
if price is None and (cost is None):
raise InvalidOrder(self.id + ' createOrder() requires a price argument or cost in params for market buy orders on spot markets to calculate the total amount to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option to False and pass in the cost to spend into the amount parameter')
else:
amountString = self.number_to_string(amount)
priceString = self.number_to_string(price)
costCalculated: Str = None
if price is not None:
costCalculated = Precise.string_mul(amountString, priceString)
else:
costCalculated = cost
request['quoteQty'] = self.cost_to_precision(symbol, costCalculated)
else:
amountCost = cost if (cost is not None) else amount
request['quoteQty'] = self.cost_to_precision(symbol, amountCost)
else:
timeInForce = self.safe_string_upper(params, 'timeInForce', 'GTC')
request['price'] = self.price_to_precision(symbol, price)
if (side == 'sell') or (type == 'limit'):
request['quantity'] = self.amount_to_precision(symbol, amount)
request['timeInForce'] = timeInForce
response = self.privateSpotPostOrder(self.extend(request, params))
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "orderId": "204371980095156544"
# }
# }
#
order = self.safe_value(response, 'result', {})
return self.parse_order(order, market)
def create_contract_order(self, symbol: str, type, side, amount, price=None, params={}):
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
'origQty': self.amount_to_precision(symbol, amount),
}
timeInForce = self.safe_string_upper(params, 'timeInForce')
if timeInForce is not None:
request['timeInForce'] = timeInForce
reduceOnly = self.safe_value(params, 'reduceOnly', False)
if side == 'buy':
requestType = 'SHORT' if (reduceOnly) else 'LONG'
request['positionSide'] = requestType
else:
requestType = 'LONG' if (reduceOnly) else 'SHORT'
request['positionSide'] = requestType
response = None
triggerPrice = self.safe_number_2(params, 'triggerPrice', 'stopPrice')
stopLoss = self.safe_number_2(params, 'stopLoss', 'triggerStopPrice')
takeProfit = self.safe_number_2(params, 'takeProfit', 'triggerProfitPrice')
isTrigger = (triggerPrice is not None)
isStopLoss = (stopLoss is not None)
isTakeProfit = (takeProfit is not None)
if price is not None:
if not (isStopLoss) and not (isTakeProfit):
request['price'] = self.price_to_precision(symbol, price)
if isTrigger:
request['timeInForce'] = self.safe_string_upper(params, 'timeInForce', 'GTC')
request['triggerPriceType'] = self.safe_string(params, 'triggerPriceType', 'LATEST_PRICE')
request['orderSide'] = side.upper()
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
entrustType = 'STOP_MARKET' if (type == 'market') else 'STOP'
request['entrustType'] = entrustType
params = self.omit(params, 'triggerPrice')
if market['linear']:
response = self.privateLinearPostFutureTradeV1EntrustCreatePlan(self.extend(request, params))
elif market['inverse']:
response = self.privateInversePostFutureTradeV1EntrustCreatePlan(self.extend(request, params))
elif isStopLoss or isTakeProfit:
if isStopLoss:
request['triggerStopPrice'] = self.price_to_precision(symbol, stopLoss)
else:
request['triggerProfitPrice'] = self.price_to_precision(symbol, takeProfit)
params = self.omit(params, ['stopLoss', 'takeProfit'])
if market['linear']:
response = self.privateLinearPostFutureTradeV1EntrustCreateProfit(self.extend(request, params))
elif market['inverse']:
response = self.privateInversePostFutureTradeV1EntrustCreateProfit(self.extend(request, params))
else:
request['orderSide'] = side.upper()
request['orderType'] = type.upper()
if market['linear']:
response = self.privateLinearPostFutureTradeV1OrderCreate(self.extend(request, params))
elif market['inverse']:
response = self.privateInversePostFutureTradeV1OrderCreate(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": "206410760006650176"
# }
#
return self.parse_order(response, market)
def fetch_order(self, id: str, symbol: str = None, params={}):
"""
fetches information on an order made by the user
https://doc.xt.com/#orderorderGet
https://doc.xt.com/#futures_ordergetById
https://doc.xt.com/#futures_entrustgetPlanById
https://doc.xt.com/#futures_entrustgetProfitById
: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 xt api endpoint
:param bool [params.trigger]: if the order is a trigger order or not
:param bool [params.stopLossTakeProfit]: if the order is a stop-loss or take-profit order
:returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
self.load_markets()
market = None
if symbol is not None:
market = self.market(symbol)
request = {}
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('fetchOrder', market, params)
subType, params = self.handle_sub_type_and_params('fetchOrder', market, params)
trigger = self.safe_value(params, 'stop')
stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit')
if trigger:
request['entrustId'] = id
elif stopLossTakeProfit:
request['profitId'] = id
else:
request['orderId'] = id
if trigger:
params = self.omit(params, 'stop')
if subType == 'inverse':
response = self.privateInverseGetFutureTradeV1EntrustPlanDetail(self.extend(request, params))
else:
response = self.privateLinearGetFutureTradeV1EntrustPlanDetail(self.extend(request, params))
elif stopLossTakeProfit:
params = self.omit(params, 'stopLossTakeProfit')
if subType == 'inverse':
response = self.privateInverseGetFutureTradeV1EntrustProfitDetail(self.extend(request, params))
else:
response = self.privateLinearGetFutureTradeV1EntrustProfitDetail(self.extend(request, params))
elif subType == 'inverse':
response = self.privateInverseGetFutureTradeV1OrderDetail(self.extend(request, params))
elif (subType == 'linear') or (type == 'swap') or (type == 'future'):
response = self.privateLinearGetFutureTradeV1OrderDetail(self.extend(request, params))
else:
response = self.privateSpotGetOrderOrderId(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "symbol": "btc_usdt",
# "orderId": "207505997850909952",
# "clientOrderId": null,
# "baseCurrency": "btc",
# "quoteCurrency": "usdt",
# "side": "BUY",
# "type": "LIMIT",
# "timeInForce": "GTC",
# "price": "20000.00",
# "origQty": "0.001000",
# "origQuoteQty": "20.00",
# "executedQty": "0.000000",
# "leavingQty": "0.001000",
# "tradeBase": "0.000000",
# "tradeQuote": "0.00",
# "avgPrice": null,
# "fee": null,
# "feeCurrency": null,
# "closed": False,
# "state": "NEW",
# "time": 1679175285162,
# "updatedTime": 1679175285255
# }
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "orderId": "211451874783183936",
# "clientOrderId": null,
# "symbol": "btc_usdt",
# "orderType": "LIMIT",
# "orderSide": "BUY",
# "positionSide": "LONG",
# "timeInForce": "GTC",
# "closePosition": False,
# "price": "20000",
# "origQty": "10",
# "avgPrice": "0",
# "executedQty": "0",
# "marginFrozen": "1.34533334",
# "remark": null,
# "triggerProfitPrice": null,
# "triggerStopPrice": null,
# "sourceId": null,
# "sourceType": "DEFAULT",
# "forceClose": False,
# "closeProfit": null,
# "state": "NEW",
# "createdTime": 1680116055693,
# "updatedTime": 1680116055693
# }
# }
#
# trigger
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "entrustId": "216300248132756992",
# "symbol": "btc_usdt",
# "entrustType": "STOP",
# "orderSide": "SELL",
# "positionSide": "SHORT",
# "timeInForce": "GTC",
# "closePosition": null,
# "price": "20000",
# "origQty": "1",
# "stopPrice": "19000",
# "triggerPriceType": "LATEST_PRICE",
# "state": "NOT_TRIGGERED",
# "marketOrderLevel": null,
# "createdTime": 1681271998064,
# "updatedTime": 1681271998064,
# "ordinary": False
# }
# }
#
# stop-loss and take-profit
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "profitId": "216306213226230400",
# "symbol": "btc_usdt",
# "positionSide": "LONG",
# "origQty": "1",
# "triggerPriceType": "LATEST_PRICE",
# "triggerProfitPrice": null,
# "triggerStopPrice": "20000",
# "entryPrice": null,
# "positionSize": null,
# "isolatedMargin": null,
# "executedQty": null,
# "avgPrice": null,
# "positionType": "ISOLATED",
# "state": "NOT_TRIGGERED",
# "createdTime": 1681273420039
# }
# }
#
order = self.safe_value(response, 'result', {})
return self.parse_order(order, market)
def fetch_orders(self, symbol: str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches information on multiple orders made by the user
https://doc.xt.com/#orderhistoryOrderGet
https://doc.xt.com/#futures_ordergetHistory
https://doc.xt.com/#futures_entrustgetPlanHistory
:param str [symbol]: unified market symbol of the market the orders were made in
:param int [since]: timestamp in ms of the earliest order
:param int [limit]: the maximum number of order structures to retrieve
:param dict params: extra parameters specific to the xt api endpoint
:param bool [params.trigger]: if the order is a trigger order or not
:returns dict[]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
self.load_markets()
request = {}
market = None
if symbol is not None:
market = self.market(symbol)
request['symbol'] = market['id']
if since is not None:
request['startTime'] = since
if limit is not None:
request['limit'] = limit
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('fetchOrders', market, params)
subType, params = self.handle_sub_type_and_params('fetchOrders', market, params)
trigger = self.safe_value_2(params, 'trigger', 'stop')
if trigger:
params = self.omit(params, ['trigger', 'stop'])
if subType == 'inverse':
response = self.privateInverseGetFutureTradeV1EntrustPlanListHistory(self.extend(request, params))
else:
response = self.privateLinearGetFutureTradeV1EntrustPlanListHistory(self.extend(request, params))
elif subType == 'inverse':
response = self.privateInverseGetFutureTradeV1OrderListHistory(self.extend(request, params))
elif (subType == 'linear') or (type == 'swap') or (type == 'future'):
response = self.privateLinearGetFutureTradeV1OrderListHistory(self.extend(request, params))
else:
marginMode = None
marginMode, params = self.handle_margin_mode_and_params('fetchOrders', params)
marginOrSpotRequest = 'LEVER' if (marginMode is not None) else 'SPOT'
request['bizType'] = marginOrSpotRequest
response = self.privateSpotGetHistoryOrder(self.extend(request, params))
#
# spot and margin
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "hasPrev": False,
# "hasNext": True,
# "items": [
# {
# "symbol": "btc_usdt",
# "orderId": "207505997850909952",
# "clientOrderId": null,
# "baseCurrency": "btc",
# "quoteCurrency": "usdt",
# "side": "BUY",
# "type": "LIMIT",
# "timeInForce": "GTC",
# "price": "20000.00",
# "origQty": "0.001000",
# "origQuoteQty": "20.00",
# "executedQty": "0.000000",
# "leavingQty": "0.000000",
# "tradeBase": "0.000000",
# "tradeQuote": "0.00",
# "avgPrice": null,
# "fee": null,
# "feeCurrency": null,
# "closed": True,
# "state": "CANCELED",
# "time": 1679175285162,
# "updatedTime": 1679175488492
# },
# ]
# }
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "hasPrev": False,
# "hasNext": True,
# "items": [
# {
# "orderId": "207519546930995456",
# "clientOrderId": null,
# "symbol": "btc_usdt",
# "orderType": "LIMIT",
# "orderSide": "BUY",
# "positionSide": "LONG",
# "timeInForce": "GTC",
# "closePosition": False,
# "price": "20000",
# "origQty": "100",
# "avgPrice": "0",
# "executedQty": "0",
# "marginFrozen": "4.12",
# "remark": null,
# "triggerProfitPrice": null,
# "triggerStopPrice": null,
# "sourceId": null,
# "sourceType": "DEFAULT",
# "forceClose": False,
# "closeProfit": null,
# "state": "CANCELED",
# "createdTime": 1679178515689,
# "updatedTime": 1679180096172
# },
# ]
# }
# }
#
# stop
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "hasPrev": False,
# "hasNext": False,
# "items": [
# {
# "entrustId": "216300248132756992",
# "symbol": "btc_usdt",
# "entrustType": "STOP",
# "orderSide": "SELL",
# "positionSide": "SHORT",
# "timeInForce": "GTC",
# "closePosition": null,
# "price": "20000",
# "origQty": "1",
# "stopPrice": "19000",
# "triggerPriceType": "LATEST_PRICE",
# "state": "USER_REVOCATION",
# "marketOrderLevel": null,
# "createdTime": 1681271998064,
# "updatedTime": 1681273188674,
# "ordinary": False
# },
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
orders = self.safe_value(data, 'items', [])
return self.parse_orders(orders, market, since, limit)
def fetch_orders_by_status(self, status, symbol: str = None, since: Int = None, limit: Int = None, params={}):
self.load_markets()
request = {}
market = None
if symbol is not None:
market = self.market(symbol)
request['symbol'] = market['id']
if limit is not None:
request['size'] = limit
if since is not None:
request['startTime'] = since
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('fetchOrdersByStatus', market, params)
subType, params = self.handle_sub_type_and_params('fetchOrdersByStatus', market, params)
trigger = self.safe_bool_2(params, 'stop', 'trigger')
stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit')
if status == 'open':
if trigger or stopLossTakeProfit:
request['state'] = 'NOT_TRIGGERED'
elif type == 'swap':
request['state'] = 'UNFINISHED' # NEW & PARTIALLY_FILLED
elif status == 'closed':
if trigger or stopLossTakeProfit:
request['state'] = 'TRIGGERED'
else:
request['state'] = 'FILLED'
elif status == 'canceled':
if trigger or stopLossTakeProfit:
request['state'] = 'USER_REVOCATION'
else:
request['state'] = 'CANCELED'
else:
request['state'] = status
if trigger or stopLossTakeProfit or (subType is not None) or (type == 'swap') or (type == 'future'):
if since is not None:
request['startTime'] = since
if limit is not None:
request['size'] = limit
if trigger:
params = self.omit(params, ['stop', 'trigger'])
if subType == 'inverse':
response = self.privateInverseGetFutureTradeV1EntrustPlanList(self.extend(request, params))
else:
response = self.privateLinearGetFutureTradeV1EntrustPlanList(self.extend(request, params))
elif stopLossTakeProfit:
params = self.omit(params, 'stopLossTakeProfit')
if subType == 'inverse':
response = self.privateInverseGetFutureTradeV1EntrustProfitList(self.extend(request, params))
else:
response = self.privateLinearGetFutureTradeV1EntrustProfitList(self.extend(request, params))
elif (subType is not None) or (type == 'swap') or (type == 'future'):
if subType == 'inverse':
response = self.privateInverseGetFutureTradeV1OrderList(self.extend(request, params))
else:
response = self.privateLinearGetFutureTradeV1OrderList(self.extend(request, params))
else:
marginMode = None
marginMode, params = self.handle_margin_mode_and_params('fetchOrdersByStatus', params)
marginOrSpotRequest = 'LEVER' if (marginMode is not None) else 'SPOT'
request['bizType'] = marginOrSpotRequest
if status != 'open':
if since is not None:
request['startTime'] = since
if limit is not None:
request = self.omit(request, 'size')
request['limit'] = limit
response = self.privateSpotGetHistoryOrder(self.extend(request, params))
else:
response = self.privateSpotGetOpenOrder(self.extend(request, params))
#
# spot and margin
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "hasPrev": False,
# "hasNext": True,
# "items": [
# {
# "symbol": "btc_usdt",
# "orderId": "207505997850909952",
# "clientOrderId": null,
# "baseCurrency": "btc",
# "quoteCurrency": "usdt",
# "side": "BUY",
# "type": "LIMIT",
# "timeInForce": "GTC",
# "price": "20000.00",
# "origQty": "0.001000",
# "origQuoteQty": "20.00",
# "executedQty": "0.000000",
# "leavingQty": "0.000000",
# "tradeBase": "0.000000",
# "tradeQuote": "0.00",
# "avgPrice": null,
# "fee": null,
# "feeCurrency": null,
# "closed": True,
# "state": "CANCELED",
# "time": 1679175285162,
# "updatedTime": 1679175488492
# },
# ]
# }
# }
#
# spot and margin: fetchOpenOrders
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": [
# {
# "symbol": "eth_usdt",
# "orderId": "208249323222264320",
# "clientOrderId": null,
# "baseCurrency": "eth",
# "quoteCurrency": "usdt",
# "side": "BUY",
# "type": "LIMIT",
# "timeInForce": "GTC",
# "price": "1300.00",
# "origQty": "0.0032",
# "origQuoteQty": "4.16",
# "executedQty": "0.0000",
# "leavingQty": "0.0032",
# "tradeBase": "0.0000",
# "tradeQuote": "0.00",
# "avgPrice": null,
# "fee": null,
# "feeCurrency": null,
# "closed": False,
# "state": "NEW",
# "time": 1679352507741,
# "updatedTime": 1679352507869
# },
# ]
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "page": 1,
# "ps": 10,
# "total": 25,
# "items": [
# {
# "orderId": "207519546930995456",
# "clientOrderId": null,
# "symbol": "btc_usdt",
# "orderType": "LIMIT",
# "orderSide": "BUY",
# "positionSide": "LONG",
# "timeInForce": "GTC",
# "closePosition": False,
# "price": "20000",
# "origQty": "100",
# "avgPrice": "0",
# "executedQty": "0",
# "marginFrozen": "4.12",
# "remark": null,
# "triggerProfitPrice": null,
# "triggerStopPrice": null,
# "sourceId": null,
# "sourceType": "DEFAULT",
# "forceClose": False,
# "closeProfit": null,
# "state": "CANCELED",
# "createdTime": 1679178515689,
# "updatedTime": 1679180096172
# },
# ]
# }
# }
#
# stop
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "page": 1,
# "ps": 3,
# "total": 8,
# "items": [
# {
# "entrustId": "216300248132756992",
# "symbol": "btc_usdt",
# "entrustType": "STOP",
# "orderSide": "SELL",
# "positionSide": "SHORT",
# "timeInForce": "GTC",
# "closePosition": null,
# "price": "20000",
# "origQty": "1",
# "stopPrice": "19000",
# "triggerPriceType": "LATEST_PRICE",
# "state": "USER_REVOCATION",
# "marketOrderLevel": null,
# "createdTime": 1681271998064,
# "updatedTime": 1681273188674,
# "ordinary": False
# },
# ]
# }
# }
#
# stop-loss and take-profit
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "page": 1,
# "ps": 3,
# "total": 2,
# "items": [
# {
# "profitId": "216306213226230400",
# "symbol": "btc_usdt",
# "positionSide": "LONG",
# "origQty": "1",
# "triggerPriceType": "LATEST_PRICE",
# "triggerProfitPrice": null,
# "triggerStopPrice": "20000",
# "entryPrice": "0",
# "positionSize": "0",
# "isolatedMargin": "0",
# "executedQty": "0",
# "avgPrice": null,
# "positionType": "ISOLATED",
# "state": "USER_REVOCATION",
# "createdTime": 1681273420039
# },
# ]
# }
# }
#
orders = []
resultDict = self.safe_dict(response, 'result')
if resultDict is not None:
orders = self.safe_list(resultDict, 'items', [])
else:
orders = self.safe_list(response, 'result')
return self.parse_orders(orders, market, since, limit)
def fetch_open_orders(self, symbol: str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch all unfilled currently open orders
https://doc.xt.com/#orderopenOrderGet
https://doc.xt.com/#futures_ordergetOrders
https://doc.xt.com/#futures_entrustgetPlan
https://doc.xt.com/#futures_entrustgetProfit
:param str [symbol]: unified market symbol of the market the orders were made in
:param int [since]: timestamp in ms of the earliest order
:param int [limit]: the maximum number of open order structures to retrieve
:param dict params: extra parameters specific to the xt api endpoint
:param bool [params.trigger]: if the order is a trigger order or not
:param bool [params.stopLossTakeProfit]: if the order is a stop-loss or take-profit order
:returns dict[]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
return self.fetch_orders_by_status('open', symbol, since, limit, params)
def fetch_closed_orders(self, symbol: str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches information on multiple closed orders made by the user
https://doc.xt.com/#orderhistoryOrderGet
https://doc.xt.com/#futures_ordergetOrders
https://doc.xt.com/#futures_entrustgetPlan
https://doc.xt.com/#futures_entrustgetProfit
:param str [symbol]: unified market symbol of the market the orders were made in
:param int [since]: timestamp in ms of the earliest order
:param int [limit]: the maximum number of order structures to retrieve
:param dict params: extra parameters specific to the xt api endpoint
:param bool [params.trigger]: if the order is a trigger order or not
:param bool [params.stopLossTakeProfit]: if the order is a stop-loss or take-profit order
:returns dict[]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
return self.fetch_orders_by_status('closed', symbol, since, limit, params)
def fetch_canceled_orders(self, symbol: str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches information on multiple canceled orders made by the user
https://doc.xt.com/#orderhistoryOrderGet
https://doc.xt.com/#futures_ordergetOrders
https://doc.xt.com/#futures_entrustgetPlan
https://doc.xt.com/#futures_entrustgetProfit
:param str [symbol]: unified market symbol of the market the orders were made in
:param int [since]: timestamp in ms of the earliest order
:param int [limit]: the maximum number of order structures to retrieve
:param dict params: extra parameters specific to the xt api endpoint
:param bool [params.trigger]: if the order is a trigger order or not
:param bool [params.stopLossTakeProfit]: if the order is a stop-loss or take-profit order
:returns dict: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
return self.fetch_orders_by_status('canceled', symbol, since, limit, params)
def cancel_order(self, id: str, symbol: str = None, params={}):
"""
cancels an open order
https://doc.xt.com/#orderorderDel
https://doc.xt.com/#futures_ordercancel
https://doc.xt.com/#futures_entrustcancelPlan
https://doc.xt.com/#futures_entrustcancelProfit
: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 xt api endpoint
:param bool [params.trigger]: if the order is a trigger order or not
:param bool [params.stopLossTakeProfit]: if the order is a stop-loss or take-profit order
:returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
self.load_markets()
market = None
if symbol is not None:
market = self.market(symbol)
request = {}
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('cancelOrder', market, params)
subType, params = self.handle_sub_type_and_params('cancelOrder', market, params)
trigger = self.safe_value_2(params, 'trigger', 'stop')
stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit')
if trigger:
request['entrustId'] = id
elif stopLossTakeProfit:
request['profitId'] = id
else:
request['orderId'] = id
if trigger:
params = self.omit(params, ['trigger', 'stop'])
if subType == 'inverse':
response = self.privateInversePostFutureTradeV1EntrustCancelPlan(self.extend(request, params))
else:
response = self.privateLinearPostFutureTradeV1EntrustCancelPlan(self.extend(request, params))
elif stopLossTakeProfit:
params = self.omit(params, 'stopLossTakeProfit')
if subType == 'inverse':
response = self.privateInversePostFutureTradeV1EntrustCancelProfitStop(self.extend(request, params))
else:
response = self.privateLinearPostFutureTradeV1EntrustCancelProfitStop(self.extend(request, params))
elif subType == 'inverse':
response = self.privateInversePostFutureTradeV1OrderCancel(self.extend(request, params))
elif (subType == 'linear') or (type == 'swap') or (type == 'future'):
response = self.privateLinearPostFutureTradeV1OrderCancel(self.extend(request, params))
else:
response = self.privateSpotDeleteOrderOrderId(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "cancelId": "208322474307982720"
# }
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": "208319789679471616"
# }
#
isContractResponse = ((subType is not None) or (type == 'swap') or (type == 'future'))
order = response if isContractResponse else self.safe_value(response, 'result', {})
return self.parse_order(order, market)
def cancel_all_orders(self, symbol: str = None, params={}):
"""
cancel all open orders in a market
https://doc.xt.com/#orderopenOrderDel
https://doc.xt.com/#futures_ordercancelBatch
https://doc.xt.com/#futures_entrustcancelPlanBatch
https://doc.xt.com/#futures_entrustcancelProfitBatch
:param str [symbol]: unified market symbol of the market to cancel orders in
:param dict params: extra parameters specific to the xt api endpoint
:param bool [params.trigger]: if the order is a trigger order or not
:param bool [params.stopLossTakeProfit]: if the order is a stop-loss or take-profit order
:returns dict[]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
self.load_markets()
request = {}
market = None
if symbol is not None:
market = self.market(symbol)
request['symbol'] = market['id']
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('cancelAllOrders', market, params)
subType, params = self.handle_sub_type_and_params('cancelAllOrders', market, params)
trigger = self.safe_value_2(params, 'trigger', 'stop')
stopLossTakeProfit = self.safe_value(params, 'stopLossTakeProfit')
if trigger:
params = self.omit(params, ['trigger', 'stop'])
if subType == 'inverse':
response = self.privateInversePostFutureTradeV1EntrustCancelAllPlan(self.extend(request, params))
else:
response = self.privateLinearPostFutureTradeV1EntrustCancelAllPlan(self.extend(request, params))
elif stopLossTakeProfit:
params = self.omit(params, 'stopLossTakeProfit')
if subType == 'inverse':
response = self.privateInversePostFutureTradeV1EntrustCancelAllProfitStop(self.extend(request, params))
else:
response = self.privateLinearPostFutureTradeV1EntrustCancelAllProfitStop(self.extend(request, params))
elif subType == 'inverse':
response = self.privateInversePostFutureTradeV1OrderCancelAll(self.extend(request, params))
elif (subType == 'linear') or (type == 'swap') or (type == 'future'):
response = self.privateLinearPostFutureTradeV1OrderCancelAll(self.extend(request, params))
else:
marginMode = None
marginMode, params = self.handle_margin_mode_and_params('cancelAllOrders', params)
marginOrSpotRequest = 'LEVER' if (marginMode is not None) else 'SPOT'
request['bizType'] = marginOrSpotRequest
response = self.privateSpotDeleteOpenOrder(self.extend(request, params))
#
# spot and margin
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": null
# }
#
# swap and future
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": True
# }
#
return [
self.safe_order(response),
]
def cancel_orders(self, ids: List[str], symbol: str = None, params={}) -> List[Order]:
"""
cancel multiple orders
https://doc.xt.com/#orderbatchOrderDel
:param str[] ids: order ids
:param str [symbol]: unified market symbol of the market to cancel orders in
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
"""
self.load_markets()
request = {
'orderIds': ids,
}
market = None
if symbol is not None:
market = self.market(symbol)
subType = None
subType, params = self.handle_sub_type_and_params('cancelOrders', market, params)
if subType is not None:
raise NotSupported(self.id + ' cancelOrders() does not support swap and future orders, only spot orders are accepted')
response = self.privateSpotDeleteBatchOrder(self.extend(request, params))
#
# spot
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": null
# }
#
return [
self.safe_order(response),
]
def parse_order(self, order, market=None):
#
# spot: createOrder
#
# {
# "orderId": "204371980095156544"
# }
#
# spot: cancelOrder
#
# {
# "cancelId": "208322474307982720"
# }
#
# swap and future: createOrder, cancelOrder, editOrder
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": "206410760006650176"
# }
#
# spot: fetchOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders, fetchOrdersByStatus
#
# {
# "symbol": "btc_usdt",
# "orderId": "207505997850909952",
# "clientOrderId": null,
# "baseCurrency": "btc",
# "quoteCurrency": "usdt",
# "side": "BUY",
# "type": "LIMIT",
# "timeInForce": "GTC",
# "price": "20000.00",
# "origQty": "0.001000",
# "origQuoteQty": "20.00",
# "executedQty": "0.000000",
# "leavingQty": "0.001000",
# "tradeBase": "0.000000",
# "tradeQuote": "0.00",
# "avgPrice": null,
# "fee": null,
# "feeCurrency": null,
# "closed": False,
# "state": "NEW",
# "time": 1679175285162,
# "updatedTime": 1679175285255
# }
#
# swap and future: fetchOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders, fetchOrdersByStatus
#
# {
# "orderId": "207519546930995456",
# "clientOrderId": null,
# "symbol": "btc_usdt",
# "orderType": "LIMIT",
# "orderSide": "BUY",
# "positionSide": "LONG",
# "timeInForce": "GTC",
# "closePosition": False,
# "price": "20000",
# "origQty": "100",
# "avgPrice": "0",
# "executedQty": "0",
# "marginFrozen": "4.12",
# "remark": null,
# "triggerProfitPrice": null,
# "triggerStopPrice": null,
# "sourceId": null,
# "sourceType": "DEFAULT",
# "forceClose": False,
# "closeProfit": null,
# "state": "CANCELED",
# "createdTime": 1679178515689,
# "updatedTime": 1679180096172
# }
#
# trigger: fetchOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders, fetchOrdersByStatus
#
# {
# "entrustId": "216300248132756992",
# "symbol": "btc_usdt",
# "entrustType": "STOP",
# "orderSide": "SELL",
# "positionSide": "SHORT",
# "timeInForce": "GTC",
# "closePosition": null,
# "price": "20000",
# "origQty": "1",
# "stopPrice": "19000",
# "triggerPriceType": "LATEST_PRICE",
# "state": "NOT_TRIGGERED",
# "marketOrderLevel": null,
# "createdTime": 1681271998064,
# "updatedTime": 1681271998064,
# "ordinary": False
# }
#
# stop-loss and take-profit: fetchOrder, fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders, fetchOrdersByStatus
#
# {
# "profitId": "216306213226230400",
# "symbol": "btc_usdt",
# "positionSide": "LONG",
# "origQty": "1",
# "triggerPriceType": "LATEST_PRICE",
# "triggerProfitPrice": null,
# "triggerStopPrice": "20000",
# "entryPrice": null,
# "positionSize": null,
# "isolatedMargin": null,
# "executedQty": null,
# "avgPrice": null,
# "positionType": "ISOLATED",
# "state": "NOT_TRIGGERED",
# "createdTime": 1681273420039
# }
#
# spot editOrder
#
# {
# "orderId": "484203027161892224",
# "modifyId": "484203544105344000",
# "clientModifyId": null
# }
#
marketId = self.safe_string(order, 'symbol')
marketType = ('result' in order) or 'contract' if ('positionSide' in order) else 'spot'
market = self.safe_market(marketId, market, None, marketType)
symbol = self.safe_symbol(marketId, market, None, marketType)
timestamp = self.safe_integer_2(order, 'time', 'createdTime')
quantity = self.safe_number(order, 'origQty')
amount = quantity if (marketType == 'spot') else Precise.string_mul(self.number_to_string(quantity), self.number_to_string(market['contractSize']))
filledQuantity = self.safe_number(order, 'executedQty')
filled = filledQuantity if (marketType == 'spot') else Precise.string_mul(self.number_to_string(filledQuantity), self.number_to_string(market['contractSize']))
lastUpdatedTimestamp = self.safe_integer(order, 'updatedTime')
return self.safe_order({
'info': order,
'id': self.safe_string_n(order, ['orderId', 'result', 'cancelId', 'entrustId', 'profitId']),
'clientOrderId': self.safe_string_2(order, 'clientOrderId', 'clientModifyId'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': lastUpdatedTimestamp,
'lastUpdateTimestamp': lastUpdatedTimestamp,
'symbol': symbol,
'type': self.safe_string_lower_2(order, 'type', 'orderType'),
'timeInForce': self.safe_string(order, 'timeInForce'),
'postOnly': None,
'side': self.safe_string_lower_2(order, 'side', 'orderSide'),
'price': self.safe_number(order, 'price'),
'triggerPrice': self.safe_number(order, 'stopPrice'),
'stopLoss': self.safe_number(order, 'triggerStopPrice'),
'takeProfit': self.safe_number(order, 'triggerProfitPrice'),
'amount': amount,
'filled': filled,
'remaining': self.safe_number(order, 'leavingQty'),
'cost': None,
'average': self.safe_number(order, 'avgPrice'),
'status': self.parse_order_status(self.safe_string(order, 'state')),
'fee': {
'currency': self.safe_currency_code(self.safe_string(order, 'feeCurrency')),
'cost': self.safe_number(order, 'fee'),
},
'trades': None,
}, market)
def parse_order_status(self, status):
statuses = {
'NEW': 'open',
'PARTIALLY_FILLED': 'open',
'FILLED': 'closed',
'CANCELED': 'canceled',
'REJECTED': 'rejected',
'EXPIRED': 'expired',
'UNFINISHED': 'open',
'NOT_TRIGGERED': 'open',
'TRIGGERING': 'open',
'TRIGGERED': 'closed',
'USER_REVOCATION': 'canceled',
'PLATFORM_REVOCATION': 'rejected',
'HISTORY': 'expired',
}
return self.safe_string(statuses, status, status)
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://doc.xt.com/#futures_usergetBalanceBill
: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 xt api endpoint
:returns dict: a `ledger structure <https://docs.ccxt.com/en/latest/manual.html#ledger-structure>`
"""
self.load_markets()
request = {}
currency = None
if code is not None:
currency = self.currency(code)
if since is not None:
request['startTime'] = since
if limit is not None:
request['limit'] = limit
type = None
subType = None
response = None
type, params = self.handle_market_type_and_params('fetchLedger', None, params)
subType, params = self.handle_sub_type_and_params('fetchLedger', None, params)
if subType == 'inverse':
response = self.privateInverseGetFutureUserV1BalanceBills(self.extend(request, params))
elif (subType == 'linear') or (type == 'swap') or (type == 'future'):
response = self.privateLinearGetFutureUserV1BalanceBills(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchLedger() does not support spot transactions, only swap and future wallet transactions are supported')
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "hasPrev": False,
# "hasNext": False,
# "items": [
# {
# "id": "207260567109387524",
# "coin": "usdt",
# "symbol": "btc_usdt",
# "type": "FEE",
# "amount": "-0.0213",
# "side": "SUB",
# "afterAmount": null,
# "createdTime": 1679116769914
# },
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
ledger = self.safe_value(data, 'items', [])
return self.parse_ledger(ledger, currency, since, limit)
def parse_ledger_entry(self, item, currency=None) -> LedgerEntry:
#
# {
# "id": "207260567109387524",
# "coin": "usdt",
# "symbol": "btc_usdt",
# "type": "FEE",
# "amount": "-0.0213",
# "side": "SUB",
# "afterAmount": null,
# "createdTime": 1679116769914
# }
#
side = self.safe_string(item, 'side')
direction = 'in' if (side == 'ADD') else 'out'
currencyId = self.safe_string(item, 'coin')
currency = self.safe_currency(currencyId, currency)
timestamp = self.safe_integer(item, 'createdTime')
return self.safe_ledger_entry({
'info': item,
'id': self.safe_string(item, 'id'),
'direction': direction,
'account': None,
'referenceId': None,
'referenceAccount': None,
'type': self.parse_ledger_entry_type(self.safe_string(item, 'type')),
'currency': self.safe_currency_code(currencyId, currency),
'amount': self.safe_number(item, 'amount'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'before': None,
'after': self.safe_number(item, 'afterAmount'),
'status': None,
'fee': {
'currency': None,
'cost': None,
},
}, currency)
def parse_ledger_entry_type(self, type):
ledgerType = {
'EXCHANGE': 'transfer',
'CLOSE_POSITION': 'trade',
'TAKE_OVER': 'trade',
'MERGE': 'trade',
'QIANG_PING_MANAGER': 'fee',
'FUND': 'fee',
'FEE': 'fee',
'ADL': 'auto-deleveraging',
}
return self.safe_string(ledgerType, type, type)
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
"""
fetch the deposit address for a currency associated with self account
https://doc.xt.com/#deposit_withdrawaldepositAddressGet
:param str code: unified currency code
:param dict params: extra parameters specific to the xt api endpoint
:param str params['network']: required network id
:returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
"""
self.load_markets()
networkCode = None
networkCode, params = self.handle_network_code_and_params(params)
currency = self.currency(code)
networkId = self.network_code_to_id(networkCode, code)
self.check_required_argument('fetchDepositAddress', networkId, 'network')
request = {
'currency': currency['id'],
'chain': networkId,
}
response = self.privateSpotGetDepositAddress(self.extend(request, params))
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "address": "0x7f7173cf29d3846d20ca5a3aec1120b93dbd157a",
# "memo": ""
# }
# }
#
result = self.safe_value(response, 'result', {})
return self.parse_deposit_address(result, currency)
def parse_deposit_address(self, depositAddress, currency=None) -> DepositAddress:
#
# {
# "address": "0x7f7173cf29d3846d20ca5a3aec1120b93dbd157a",
# "memo": ""
# }
#
address = self.safe_string(depositAddress, 'address')
self.check_address(address)
return {
'info': depositAddress,
'currency': self.safe_currency_code(None, currency),
'network': None,
'address': address,
'tag': self.safe_string(depositAddress, 'memo'),
}
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch all deposits made to an account
https://doc.xt.com/#deposit_withdrawalhistoryDepositGet
: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 transaction structures to retrieve
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
"""
self.load_markets()
request = {}
currency = None
if code is not None:
currency = self.currency(code)
request['currency'] = currency['id']
if since is not None:
request['startTime'] = since
if limit is not None:
request['limit'] = limit # default 10, max 200
response = self.privateSpotGetDepositHistory(self.extend(request, params))
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "hasPrev": False,
# "hasNext": False,
# "items": [
# {
# "id": 170368702,
# "currency": "usdt",
# "chain": "Ethereum",
# "memo": "",
# "status": "SUCCESS",
# "amount": "31.792528",
# "confirmations": 12,
# "transactionId": "0x90b8487c258b81b85e15e461b1839c49d4d8e6e9de4c1adb658cd47d4f5c5321",
# "address": "0x7f7172cf29d3846d30ca5a3aec1120b92dbd150b",
# "fromAddr": "0x7830c87c02e56aff27fa9ab1241711331fa86f58",
# "createdTime": 1678491442000
# },
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
deposits = self.safe_value(data, 'items', [])
return self.parse_transactions(deposits, currency, since, limit, params)
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch all withdrawals made from an account
https://doc.xt.com/#deposit_withdrawalwithdrawHistory
: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 transaction structures to retrieve
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
"""
self.load_markets()
request = {}
currency = None
if code is not None:
currency = self.currency(code)
request['currency'] = currency['id']
if since is not None:
request['startTime'] = since
if limit is not None:
request['limit'] = limit # default 10, max 200
response = self.privateSpotGetWithdrawHistory(self.extend(request, params))
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "hasPrev": False,
# "hasNext": False,
# "items": [
# {
# "id": 950898,
# "currency": "usdt",
# "chain": "Tron",
# "address": "TGB2vxTjiqraVZBy7YHXF8V3CSMVhQKcaf",
# "memo": "",
# "status": "SUCCESS",
# "amount": "5",
# "fee": "2",
# "confirmations": 6,
# "transactionId": "c36e230b879842b1d7afd19d15ee1a866e26eaa0626e367d6f545d2932a15156",
# "createdTime": 1680049062000
# }
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
withdrawals = self.safe_value(data, 'items', [])
return self.parse_transactions(withdrawals, currency, since, limit, params)
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
"""
make a withdrawal
https://doc.xt.com/#deposit_withdrawalwithdraw
: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 xt api endpoint
:returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
"""
self.check_address(address)
self.load_markets()
currency = self.currency(code)
tag, params = self.handle_withdraw_tag_and_params(tag, params)
networkCode = None
networkCode, params = self.handle_network_code_and_params(params)
networkIdsByCodes = self.safe_value(self.options, 'networks', {})
networkId = self.safe_string_2(networkIdsByCodes, networkCode, code, code)
request = {
'currency': currency['id'],
'chain': networkId,
'amount': self.currency_to_precision(code, amount),
'address': address,
}
if tag is not None:
request['memo'] = tag
response = self.privateSpotPostWithdraw(self.extend(request, params))
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "id": 950898
# }
# }
#
result = self.safe_value(response, 'result', {})
return self.parse_transaction(result, currency)
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
#
# fetchDeposits
#
# {
# "id": 170368702,
# "currency": "usdt",
# "chain": "Ethereum",
# "memo": "",
# "status": "SUCCESS",
# "amount": "31.792528",
# "confirmations": 12,
# "transactionId": "0x90b8487c258b81b85e15e461b1839c49d4d8e6e9de4c1adb658cd47d4f5c5321",
# "address": "0x7f7172cf29d3846d30ca5a3aec1120b92dbd150b",
# "fromAddr": "0x7830c87c02e56aff27fa9ab1241711331fa86f58",
# "createdTime": 1678491442000
# }
#
# fetchWithdrawals
#
# {
# "id": 950898,
# "currency": "usdt",
# "chain": "Tron",
# "address": "TGB2vxTjiqraVZBy7YHXF8V3CSMVhQKcaf",
# "memo": "",
# "status": "SUCCESS",
# "amount": "5",
# "fee": "2",
# "confirmations": 6,
# "transactionId": "c36e230b879842b1d7afd19d15ee1a866e26eaa0626e367d6f545d2932a15156",
# "createdTime": 1680049062000
# }
#
# withdraw
#
# {
# "id": 950898
# }
#
type = 'deposit' if ('fromAddr' in transaction) else 'withdraw'
timestamp = self.safe_integer(transaction, 'createdTime')
address = self.safe_string(transaction, 'address')
memo = self.safe_string(transaction, 'memo')
currencyCode = self.safe_currency_code(self.safe_string(transaction, 'currency'), currency)
fee = self.safe_number(transaction, 'fee')
feeCurrency = currencyCode if (fee is not None) else None
networkId = self.safe_string(transaction, 'chain')
return {
'info': transaction,
'id': self.safe_string(transaction, 'id'),
'txid': self.safe_string(transaction, 'transactionId'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'updated': None,
'addressFrom': self.safe_string(transaction, 'fromAddr'),
'addressTo': address,
'address': address,
'tagFrom': None,
'tagTo': None,
'tag': memo,
'type': type,
'amount': self.safe_number(transaction, 'amount'),
'currency': currencyCode,
'network': self.network_id_to_code(networkId, currencyCode),
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
'comment': memo,
'fee': {
'currency': feeCurrency,
'cost': fee,
'rate': None,
},
'internal': None,
}
def parse_transaction_status(self, status):
statuses = {
'SUBMIT': 'pending',
'REVIEW': 'pending',
'AUDITED': 'pending',
'PENDING': 'pending',
'CANCEL': 'canceled',
'FAIL': 'failed',
'SUCCESS': 'ok',
}
return self.safe_string(statuses, status, status)
def set_leverage(self, leverage: int, symbol: str = None, params={}):
"""
set the level of leverage for a market
https://doc.xt.com/#futures_useradjustLeverage
:param float leverage: the rate of leverage
:param str symbol: unified market symbol
:param dict params: extra parameters specific to the xt api endpoint
:param str params['positionSide']: 'LONG' or 'SHORT'
:returns dict: response from the exchange
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
positionSide = self.safe_string(params, 'positionSide')
self.check_required_argument('setLeverage', positionSide, 'positionSide', ['LONG', 'SHORT'])
if (leverage < 1) or (leverage > 125):
raise BadRequest(self.id + ' setLeverage() leverage should be between 1 and 125')
self.load_markets()
market = self.market(symbol)
if not (market['contract']):
raise BadSymbol(self.id + ' setLeverage() supports contract markets only')
request = {
'symbol': market['id'],
'positionSide': positionSide,
'leverage': leverage,
}
subType = None
subType, params = self.handle_sub_type_and_params('setLeverage', market, params)
response = None
if subType == 'inverse':
response = self.privateInversePostFutureUserV1PositionAdjustLeverage(self.extend(request, params))
else:
response = self.privateLinearPostFutureUserV1PositionAdjustLeverage(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": null
# }
#
return response
def add_margin(self, symbol: str, amount: float, params={}):
"""
add margin to a position
https://doc.xt.com/#futures_useradjustMargin
:param str symbol: unified market symbol
:param float amount: amount of margin to add
:param dict params: extra parameters specific to the xt api endpoint
:param str params['positionSide']: 'LONG' or 'SHORT'
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
"""
return self.modify_margin_helper(symbol, amount, 'ADD', params)
def reduce_margin(self, symbol: str, amount: float, params={}):
"""
remove margin from a position
https://doc.xt.com/#futures_useradjustMargin
:param str symbol: unified market symbol
:param float amount: the amount of margin to remove
:param dict params: extra parameters specific to the xt api endpoint
:param str params['positionSide']: 'LONG' or 'SHORT'
:returns dict: a `margin structure <https://docs.ccxt.com/#/?id=reduce-margin-structure>`
"""
return self.modify_margin_helper(symbol, amount, 'SUB', params)
def modify_margin_helper(self, symbol: str, amount, addOrReduce, params={}) -> MarginModification:
positionSide = self.safe_string(params, 'positionSide')
self.check_required_argument('setLeverage', positionSide, 'positionSide', ['LONG', 'SHORT'])
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
'margin': amount,
'type': addOrReduce,
'positionSide': positionSide,
}
subType = None
subType, params = self.handle_sub_type_and_params('modifyMarginHelper', market, params)
response = None
if subType == 'inverse':
response = self.privateInversePostFutureUserV1PositionMargin(self.extend(request, params))
else:
response = self.privateLinearPostFutureUserV1PositionMargin(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": null
# }
#
return self.parse_margin_modification(response, market)
def parse_margin_modification(self, data, market=None) -> MarginModification:
return {
'info': data,
'type': None,
'amount': None,
'code': None,
'symbol': self.safe_symbol(None, market),
'status': None,
'marginMode': None,
'total': None,
'timestamp': None,
'datetime': None,
}
def fetch_leverage_tiers(self, symbols: List[str] = None, params={}) -> LeverageTiers:
"""
retrieve information on the maximum leverage for different trade sizes
https://doc.xt.com/#futures_quotesgetLeverageBrackets
:param str [symbols]: a list of unified market symbols
:param dict params: extra parameters specific to the xt api endpoint
:returns dict: a dictionary of `leverage tiers structures <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`
"""
self.load_markets()
subType = None
subType, params = self.handle_sub_type_and_params('fetchLeverageTiers', None, params)
response = None
if subType == 'inverse':
response = self.publicInverseGetFutureMarketV1PublicLeverageBracketList(params)
else:
response = self.publicLinearGetFutureMarketV1PublicLeverageBracketList(params)
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "symbol": "rad_usdt",
# "leverageBrackets": [
# {
# "symbol": "rad_usdt",
# "bracket": 1,
# "maxNominalValue": "5000",
# "maintMarginRate": "0.025",
# "startMarginRate": "0.05",
# "maxStartMarginRate": null,
# "maxLeverage": "20",
# "minLeverage": "1"
# },
# ]
# },
# ]
# }
#
data = self.safe_value(response, 'result', [])
symbols = self.market_symbols(symbols)
return self.parse_leverage_tiers(data, symbols, 'symbol')
def parse_leverage_tiers(self, response, symbols=None, marketIdKey=None) -> LeverageTiers:
#
# {
# "symbol": "rad_usdt",
# "leverageBrackets": [
# {
# "symbol": "rad_usdt",
# "bracket": 1,
# "maxNominalValue": "5000",
# "maintMarginRate": "0.025",
# "startMarginRate": "0.05",
# "maxStartMarginRate": null,
# "maxLeverage": "20",
# "minLeverage": "1"
# },
# ]
# }
#
result = {}
for i in range(0, len(response)):
entry = response[i]
marketId = self.safe_string(entry, 'symbol')
market = self.safe_market(marketId, None, '_', 'contract')
symbol = self.safe_symbol(marketId, market)
if symbols is not None:
if self.in_array(symbol, symbols):
result[symbol] = self.parse_market_leverage_tiers(entry, market)
else:
result[symbol] = self.parse_market_leverage_tiers(response[i], market)
return result
def fetch_market_leverage_tiers(self, symbol: str, params={}) -> List[LeverageTier]:
"""
retrieve information on the maximum leverage for different trade sizes of a single market
https://doc.xt.com/#futures_quotesgetLeverageBracket
:param str symbol: unified market symbol
:param dict params: extra parameters specific to the xt api endpoint
:returns dict: a `leverage tiers structure <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`
"""
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
}
subType = None
subType, params = self.handle_sub_type_and_params('fetchMarketLeverageTiers', market, params)
response = None
if subType == 'inverse':
response = self.publicInverseGetFutureMarketV1PublicLeverageBracketDetail(self.extend(request, params))
else:
response = self.publicLinearGetFutureMarketV1PublicLeverageBracketDetail(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "symbol": "btc_usdt",
# "leverageBrackets": [
# {
# "symbol": "btc_usdt",
# "bracket": 1,
# "maxNominalValue": "500000",
# "maintMarginRate": "0.004",
# "startMarginRate": "0.008",
# "maxStartMarginRate": null,
# "maxLeverage": "125",
# "minLeverage": "1"
# },
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
return self.parse_market_leverage_tiers(data, market)
def parse_market_leverage_tiers(self, info, market=None) -> List[LeverageTier]:
#
# {
# "symbol": "rad_usdt",
# "leverageBrackets": [
# {
# "symbol": "rad_usdt",
# "bracket": 1,
# "maxNominalValue": "5000",
# "maintMarginRate": "0.025",
# "startMarginRate": "0.05",
# "maxStartMarginRate": null,
# "maxLeverage": "20",
# "minLeverage": "1"
# },
# ]
# }
#
tiers = []
brackets = self.safe_value(info, 'leverageBrackets', [])
for i in range(0, len(brackets)):
tier = brackets[i]
marketId = self.safe_string(info, 'symbol')
market = self.safe_market(marketId, market, '_', 'contract')
tiers.append({
'tier': self.safe_integer(tier, 'bracket'),
'symbol': self.safe_symbol(marketId, market, '_', 'contract'),
'currency': market['settle'],
'minNotional': self.safe_number(brackets[i - 1], 'maxNominalValue', 0),
'maxNotional': self.safe_number(tier, 'maxNominalValue'),
'maintenanceMarginRate': self.safe_number(tier, 'maintMarginRate'),
'maxLeverage': self.safe_number(tier, 'maxLeverage'),
'info': tier,
})
return tiers
def fetch_funding_rate_history(self, symbol: str = None, since: Int = None, limit: Int = None, params={}):
"""
fetches historical funding rates
https://doc.xt.com/#futures_quotesgetFundingRateRecord
: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] to fetch
:param dict params: extra parameters specific to the xt api endpoint
:param bool params['paginate']: True/false whether to use the pagination helper to aumatically paginate through the results
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/en/latest/manual.html?#funding-rate-history-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
self.load_markets()
paginate = False
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
if paginate:
return self.fetch_paginated_call_cursor('fetchFundingRateHistory', symbol, since, limit, params, 'id', 'id', 1, 200)
market = self.market(symbol)
if not market['swap']:
raise BadSymbol(self.id + ' fetchFundingRateHistory() supports swap contracts only')
request = {
'symbol': market['id'],
}
if limit is not None:
request['limit'] = limit
else:
request['limit'] = 200 # max
subType = None
subType, params = self.handle_sub_type_and_params('fetchFundingRateHistory', market, params)
response = None
if subType == 'inverse':
response = self.publicInverseGetFutureMarketV1PublicQFundingRateRecord(self.extend(request, params))
else:
response = self.publicLinearGetFutureMarketV1PublicQFundingRateRecord(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "hasPrev": False,
# "hasNext": True,
# "items": [
# {
# "id": "210441653482221888",
# "symbol": "btc_usdt",
# "fundingRate": "0.000057",
# "createdTime": 1679875200000,
# "collectionInternal": 28800
# },
# ]
# }
# }
#
result = self.safe_value(response, 'result', {})
items = self.safe_value(result, 'items', [])
rates = []
for i in range(0, len(items)):
entry = items[i]
marketId = self.safe_string(entry, 'symbol')
symbolInner = self.safe_symbol(marketId, market)
timestamp = self.safe_integer(entry, 'createdTime')
rates.append({
'info': entry,
'symbol': symbolInner,
'fundingRate': self.safe_number(entry, 'fundingRate'),
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
})
sorted = self.sort_by(rates, 'timestamp')
return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)
def fetch_funding_interval(self, symbol: str, params={}) -> FundingRate:
"""
fetch the current funding rate interval
https://doc.xt.com/#futures_quotesgetFundingRate
: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>`
"""
return self.fetch_funding_rate(symbol, params)
def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
"""
fetch the current funding rate
https://doc.xt.com/#futures_quotesgetFundingRate
:param str symbol: unified market symbol
:param dict params: extra parameters specific to the xt api endpoint
:returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
"""
self.load_markets()
market = self.market(symbol)
if not market['swap']:
raise BadSymbol(self.id + ' fetchFundingRate() supports swap contracts only')
request = {
'symbol': market['id'],
}
subType = None
subType, params = self.handle_sub_type_and_params('fetchFundingRate', market, params)
response = None
if subType == 'inverse':
response = self.publicInverseGetFutureMarketV1PublicQFundingRate(self.extend(request, params))
else:
response = self.publicLinearGetFutureMarketV1PublicQFundingRate(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "symbol": "btc_usdt",
# "fundingRate": "0.000086",
# "nextCollectionTime": 1680307200000,
# "collectionInternal": 8
# }
# }
#
result = self.safe_value(response, 'result', {})
return self.parse_funding_rate(result, market)
def parse_funding_rate(self, contract, market=None) -> FundingRate:
#
# {
# "symbol": "btc_usdt",
# "fundingRate": "0.000086",
# "nextCollectionTime": 1680307200000,
# "collectionInternal": 8
# }
#
marketId = self.safe_string(contract, 'symbol')
symbol = self.safe_symbol(marketId, market, '_', 'swap')
timestamp = self.safe_integer(contract, 'nextCollectionTime')
interval = self.safe_string(contract, 'collectionInternal')
if interval is not None:
interval = interval + 'h'
return {
'info': contract,
'symbol': symbol,
'markPrice': None,
'indexPrice': None,
'interestRate': None,
'estimatedSettlePrice': None,
'timestamp': None,
'datetime': None,
'fundingRate': self.safe_number(contract, 'fundingRate'),
'fundingTimestamp': timestamp,
'fundingDatetime': self.iso8601(timestamp),
'nextFundingRate': None,
'nextFundingTimestamp': None,
'nextFundingDatetime': None,
'previousFundingRate': None,
'previousFundingTimestamp': None,
'previousFundingDatetime': None,
'interval': interval,
}
def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch the funding history
https://doc.xt.com/#futures_usergetFunding
:param str symbol: unified market symbol
:param int [since]: the starting timestamp in milliseconds
:param int [limit]: the number of entries to return
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: a list of `funding history structures <https://docs.ccxt.com/#/?id=funding-history-structure>`
"""
self.load_markets()
market = self.market(symbol)
if not market['swap']:
raise BadSymbol(self.id + ' fetchFundingHistory() supports swap contracts only')
request = {
'symbol': market['id'],
}
if since is not None:
request['startTime'] = since
if limit is not None:
request['limit'] = limit
subType = None
subType, params = self.handle_sub_type_and_params('fetchFundingHistory', market, params)
response = None
if subType == 'inverse':
response = self.privateInverseGetFutureUserV1BalanceFundingRateList(self.extend(request, params))
else:
response = self.privateLinearGetFutureUserV1BalanceFundingRateList(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": {
# "hasPrev": False,
# "hasNext": False,
# "items": [
# {
# "id": "210804044057280512",
# "symbol": "btc_usdt",
# "cast": "-0.0013",
# "coin": "usdt",
# "positionSide": "SHORT",
# "createdTime": 1679961600653
# },
# ]
# }
# }
#
data = self.safe_value(response, 'result', {})
items = self.safe_value(data, 'items', [])
result = []
for i in range(0, len(items)):
entry = items[i]
result.append(self.parse_funding_history(entry, market))
sorted = self.sort_by(result, 'timestamp')
return self.filter_by_since_limit(sorted, since, limit)
def parse_funding_history(self, contract, market=None):
#
# {
# "id": "210804044057280512",
# "symbol": "btc_usdt",
# "cast": "-0.0013",
# "coin": "usdt",
# "positionSide": "SHORT",
# "createdTime": 1679961600653
# }
#
marketId = self.safe_string(contract, 'symbol')
symbol = self.safe_symbol(marketId, market, '_', 'swap')
currencyId = self.safe_string(contract, 'coin')
code = self.safe_currency_code(currencyId)
timestamp = self.safe_integer(contract, 'createdTime')
return {
'info': contract,
'symbol': symbol,
'code': code,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'id': self.safe_string(contract, 'id'),
'amount': self.safe_number(contract, 'cast'),
}
def fetch_position(self, symbol: str, params={}):
"""
fetch data on a single open contract trade position
https://doc.xt.com/#futures_usergetPosition
:param str symbol: unified market symbol of the market the position is held in
:param dict params: extra parameters specific to the xt api endpoint
:returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
"""
self.load_markets()
market = self.market(symbol)
request = {
'symbol': market['id'],
}
subType = None
subType, params = self.handle_sub_type_and_params('fetchPosition', market, params)
response = None
if subType == 'inverse':
response = self.privateInverseGetFutureUserV1PositionList(self.extend(request, params))
else:
response = self.privateLinearGetFutureUserV1PositionList(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "symbol": "btc_usdt",
# "positionType": "ISOLATED",
# "positionSide": "SHORT",
# "contractType": "PERPETUAL",
# "positionSize": "10",
# "closeOrderSize": "0",
# "availableCloseSize": "10",
# "entryPrice": "27060",
# "openOrderSize": "0",
# "isolatedMargin": "1.0824",
# "openOrderMarginFrozen": "0",
# "realizedProfit": "-0.00130138",
# "autoMargin": False,
# "leverage": 25
# },
# ]
# }
#
positions = self.safe_value(response, 'result', [])
for i in range(0, len(positions)):
entry = positions[i]
marketId = self.safe_string(entry, 'symbol')
marketInner = self.safe_market(marketId, None, None, 'contract')
positionSize = self.safe_string(entry, 'positionSize')
if positionSize != '0':
return self.parse_position(entry, marketInner)
return None
def fetch_positions(self, symbols: List[str] = None, params={}) -> List[Position]:
"""
fetch all open positions
https://doc.xt.com/#futures_usergetPosition
:param str [symbols]: list of unified market symbols, not supported with xt
:param dict params: extra parameters specific to the xt api endpoint
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
"""
self.load_markets()
subType = None
subType, params = self.handle_sub_type_and_params('fetchPositions', None, params)
response = None
if subType == 'inverse':
response = self.privateInverseGetFutureUserV1PositionList(params)
else:
response = self.privateLinearGetFutureUserV1PositionList(params)
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": [
# {
# "symbol": "btc_usdt",
# "positionType": "ISOLATED",
# "positionSide": "SHORT",
# "contractType": "PERPETUAL",
# "positionSize": "10",
# "closeOrderSize": "0",
# "availableCloseSize": "10",
# "entryPrice": "27060",
# "openOrderSize": "0",
# "isolatedMargin": "1.0824",
# "openOrderMarginFrozen": "0",
# "realizedProfit": "-0.00130138",
# "autoMargin": False,
# "leverage": 25
# },
# ]
# }
#
positions = self.safe_value(response, 'result', [])
result = []
for i in range(0, len(positions)):
entry = positions[i]
marketId = self.safe_string(entry, 'symbol')
marketInner = self.safe_market(marketId, None, None, 'contract')
result.append(self.parse_position(entry, marketInner))
return self.filter_by_array_positions(result, 'symbol', symbols, False)
def parse_position(self, position, market=None):
#
# {
# "symbol": "btc_usdt",
# "positionType": "ISOLATED",
# "positionSide": "SHORT",
# "contractType": "PERPETUAL",
# "positionSize": "10",
# "closeOrderSize": "0",
# "availableCloseSize": "10",
# "entryPrice": "27060",
# "openOrderSize": "0",
# "isolatedMargin": "1.0824",
# "openOrderMarginFrozen": "0",
# "realizedProfit": "-0.00130138",
# "autoMargin": False,
# "leverage": 25
# }
#
marketId = self.safe_string(position, 'symbol')
market = self.safe_market(marketId, market, None, 'contract')
symbol = self.safe_symbol(marketId, market, None, 'contract')
positionType = self.safe_string(position, 'positionType')
marginMode = 'cross' if (positionType == 'CROSSED') else 'isolated'
collateral = self.safe_number(position, 'isolatedMargin')
return self.safe_position({
'info': position,
'id': None,
'symbol': symbol,
'timestamp': None,
'datetime': None,
'hedged': None,
'side': self.safe_string_lower(position, 'positionSide'),
'contracts': self.safe_number(position, 'positionSize'),
'contractSize': market['contractSize'],
'entryPrice': self.safe_number(position, 'entryPrice'),
'markPrice': None,
'notional': None,
'leverage': self.safe_integer(position, 'leverage'),
'collateral': collateral,
'initialMargin': collateral,
'maintenanceMargin': None,
'initialMarginPercentage': None,
'maintenanceMarginPercentage': None,
'unrealizedPnl': None,
'liquidationPrice': None,
'marginMode': marginMode,
'percentage': None,
'marginRatio': None,
})
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
"""
transfer currency internally between wallets on the same account
https://doc.xt.com/#transfersubTransferPost
:param str code: unified currency code
:param float amount: amount to transfer
:param str fromAccount: account to transfer from - spot, swap, leverage, finance
:param str toAccount: account to transfer to - spot, swap, leverage, finance
:param dict params: extra parameters specific to the whitebit api endpoint
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
"""
self.load_markets()
currency = self.currency(code)
accountsByType = self.safe_value(self.options, 'accountsById')
fromAccountId = self.safe_string(accountsByType, fromAccount, fromAccount)
toAccountId = self.safe_string(accountsByType, toAccount, toAccount)
amountString = self.currency_to_precision(code, amount)
request = {
'bizId': self.uuid(),
'currency': currency['id'],
'amount': amountString,
'from': fromAccountId,
'to': toAccountId,
}
response = self.privateSpotPostBalanceTransfer(self.extend(request, params))
#
# {
# info: {rc: '0', mc: 'SUCCESS', ma: [], result: '226971333791398656'},
# id: '226971333791398656',
# timestamp: None,
# datetime: None,
# currency: None,
# amount: None,
# fromAccount: None,
# toAccount: None,
# status: None
# }
#
return self.parse_transfer(response, currency)
def parse_transfer(self, transfer, currency=None):
return {
'info': transfer,
'id': self.safe_string(transfer, 'result'),
'timestamp': None,
'datetime': None,
'currency': None,
'amount': None,
'fromAccount': None,
'toAccount': None,
'status': None,
}
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
"""
set margin mode to 'cross' or 'isolated'
https://doc.xt.com/#futures_userchangePositionType
:param str marginMode: 'cross' or 'isolated'
:param str [symbol]: required
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.positionSide]: *required* "long" or "short"
:returns dict: response from the exchange
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
self.load_markets()
market = self.market(symbol)
if market['spot']:
raise BadSymbol(self.id + ' setMarginMode() supports contract markets only')
marginMode = marginMode.lower()
if marginMode != 'isolated' and marginMode != 'cross':
raise BadRequest(self.id + ' setMarginMode() marginMode argument should be isolated or cross')
if marginMode == 'cross':
marginMode = 'CROSSED'
else:
marginMode = 'ISOLATED'
posSide = self.safe_string_upper(params, 'positionSide')
if posSide is None:
raise ArgumentsRequired(self.id + ' setMarginMode() requires a positionSide parameter, either "LONG" or "SHORT"')
request: dict = {
'positionType': marginMode,
'positionSide': posSide,
'symbol': market['id'],
}
response = self.privateLinearPostFutureUserV1PositionChangeType(self.extend(request, params))
#
# {
# "error": {
# "code": "",
# "msg": ""
# },
# "msgInfo": "",
# "result": {},
# "returnCode": 0
# }
#
return response # unify return type
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order:
"""
cancels an order and places a new order
https://doc.xt.com/#orderorderUpdate
https://doc.xt.com/#futures_orderupdate
https://doc.xt.com/#futures_entrustupdateProfit
:param str id: order id
:param str symbol: unified symbol of the market to create an order in
:param str type: 'market' or 'limit'
:param str side: 'buy' or 'sell'
:param float amount: how much of the currency you want to trade in units of the base currency
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
:param dict [params]: extra parameters specific to the exchange API endpoint
:param float [params.stopLoss]: price to set a stop-loss on an open position
:param float [params.takeProfit]: price to set a take-profit on an open position
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
if amount is None:
raise ArgumentsRequired(self.id + ' editOrder() requires an amount argument')
self.load_markets()
market = self.market(symbol)
request = {}
stopLoss = self.safe_number_2(params, 'stopLoss', 'triggerStopPrice')
takeProfit = self.safe_number_2(params, 'takeProfit', 'triggerProfitPrice')
params = self.omit(params, ['stopLoss', 'takeProfit'])
isStopLoss = (stopLoss is not None)
isTakeProfit = (takeProfit is not None)
if isStopLoss or isTakeProfit:
request['profitId'] = id
else:
request['orderId'] = id
request['price'] = self.price_to_precision(symbol, price)
response = None
if market['swap']:
if isStopLoss:
request['triggerStopPrice'] = self.price_to_precision(symbol, stopLoss)
elif takeProfit is not None:
request['triggerProfitPrice'] = self.price_to_precision(symbol, takeProfit)
else:
request['origQty'] = self.amount_to_precision(symbol, amount)
subType = None
subType, params = self.handle_sub_type_and_params('editOrder', market, params)
if subType == 'inverse':
if isStopLoss or isTakeProfit:
response = self.privateInversePostFutureTradeV1EntrustUpdateProfitStop(self.extend(request, params))
else:
response = self.privateInversePostFutureTradeV1OrderUpdate(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": "483869474947826752"
# }
#
else:
if isStopLoss or isTakeProfit:
response = self.privateLinearPostFutureTradeV1EntrustUpdateProfitStop(self.extend(request, params))
else:
response = self.privateLinearPostFutureTradeV1OrderUpdate(self.extend(request, params))
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": "483869474947826752"
# }
#
else:
request['quantity'] = self.amount_to_precision(symbol, amount)
response = self.privateSpotPutOrderOrderId(self.extend(request, params))
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {
# "orderId": "484203027161892224",
# "modifyId": "484203544105344000",
# "clientModifyId": null
# }
# }
#
result = response if (market['swap']) else self.safe_dict(response, 'result', {})
return self.parse_order(result, market)
def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
#
# spot: error
#
# {
# "rc": 1,
# "mc": "AUTH_103",
# "ma": [],
# "result": null
# }
#
# spot: success
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": []
# }
#
# swap and future: error
#
# {
# "returnCode": 1,
# "msgInfo": "failure",
# "error": {
# "code": "403",
# "msg": "invalid signature"
# },
# "result": null
# }
#
# swap and future: success
#
# {
# "returnCode": 0,
# "msgInfo": "success",
# "error": null,
# "result": null
# }
#
# other:
#
# {
# "rc": 0,
# "mc": "SUCCESS",
# "ma": [],
# "result": {}
# }
#
# {"returnCode":1,"msgInfo":"failure","error":{"code":"insufficient_balance","msg":"insufficient balance","args":[]},"result":null}
#
#
status = self.safe_string_upper_2(response, 'msgInfo', 'mc')
if status is not None and status != 'SUCCESS':
feedback = self.id + ' ' + body
error = self.safe_value(response, 'error', {})
spotErrorCode = self.safe_string(response, 'mc')
errorCode = self.safe_string(error, 'code', spotErrorCode)
spotMessage = self.safe_string(response, 'msgInfo')
message = self.safe_string(error, 'msg', spotMessage)
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
raise ExchangeError(feedback)
return None
def sign(self, path, api=[], method='GET', params={}, headers=None, body=None):
signed = api[0] == 'private'
endpoint = api[1]
request = '/' + self.implode_params(path, params)
payload = None
if (endpoint == 'spot') or (endpoint == 'user'):
if signed:
payload = '/' + self.version + request
else:
payload = '/' + self.version + '/public' + request
else:
payload = request
url = self.urls['api'][endpoint] + payload
query = self.omit(params, self.extract_params(path))
urlencoded = self.urlencode(self.keysort(query))
headers = {
'Content-Type': 'application/json',
}
if signed:
self.check_required_credentials()
defaultRecvWindow = self.safe_string(self.options, 'recvWindow')
recvWindow = self.safe_string(query, 'recvWindow', defaultRecvWindow)
timestamp = self.number_to_string(self.nonce())
body = query
if (payload == '/v4/order') or (payload == '/future/trade/v1/order/create') or (payload == '/future/trade/v1/entrust/create-plan') or (payload == '/future/trade/v1/entrust/create-profit') or (payload == '/future/trade/v1/order/create-batch'):
id = 'CCXT'
if payload.find('future') > -1:
body['clientMedia'] = id
else:
body['media'] = id
isUndefinedBody = ((method == 'GET') or (path == 'order/{orderId}') or (path == 'ws-token'))
if (method == 'PUT') and (endpoint == 'spot'):
isUndefinedBody = False
body = None if isUndefinedBody else self.json(body)
payloadString = None
if (endpoint == 'spot') or (endpoint == 'user'):
payloadString = 'xt-validate-algorithms=HmacSHA256&xt-validate-appkey=' + self.apiKey + '&xt-validate-recvwindow=' + recvWindow + '&xt-validate-t' + 'imestamp=' + timestamp
if isUndefinedBody:
if urlencoded:
url += '?' + urlencoded
payloadString += '#' + method + '#' + payload + '#' + self.rawencode(self.keysort(query))
else:
payloadString += '#' + method + '#' + payload
else:
payloadString += '#' + method + '#' + payload + '#' + body
headers['xt-validate-algorithms'] = 'HmacSHA256'
headers['xt-validate-recvwindow'] = recvWindow
else:
payloadString = 'xt-validate-appkey=' + self.apiKey + '&xt-validate-t' + 'imestamp=' + timestamp # we can't glue timestamp, breaks in php
if method == 'GET':
if urlencoded:
url += '?' + urlencoded
payloadString += '#' + payload + '#' + urlencoded
else:
payloadString += '#' + payload
else:
payloadString += '#' + payload + '#' + body
signature = self.hmac(self.encode(payloadString), self.encode(self.secret), hashlib.sha256)
headers['xt-validate-appkey'] = self.apiKey
headers['xt-validate-timestamp'] = timestamp
headers['xt-validate-signature'] = signature
else:
if urlencoded:
url += '?' + urlencoded
return {'url': url, 'method': method, 'body': body, 'headers': headers}