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

3197 lines
139 KiB
Python

# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
from ccxt.async_support.base.exchange import Exchange
from ccxt.abstract.bitrue import ImplicitAPI
import asyncio
import hashlib
import json
from ccxt.base.types import Any, Balances, Currencies, Currency, Int, MarginModification, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import OrderImmediatelyFillable
from ccxt.base.errors import NotSupported
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import InvalidNonce
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise
class bitrue(Exchange, ImplicitAPI):
def describe(self) -> Any:
return self.deep_extend(super(bitrue, self).describe(), {
'id': 'bitrue',
'name': 'Bitrue',
'countries': ['SG'], # Singapore, Malta
'rateLimit': 10,
'certified': False,
'version': 'v1',
'pro': True,
# new metainfo interface
'has': {
'CORS': None,
'spot': True,
'margin': False,
'swap': True,
'future': False,
'option': False,
'addMargin': False,
'borrowCrossMargin': False,
'borrowIsolatedMargin': False,
'borrowMargin': False,
'cancelAllOrders': True,
'cancelOrder': True,
'closeAllPositions': False,
'closePosition': False,
'createMarketBuyOrderWithCost': True,
'createMarketOrderWithCost': False,
'createMarketSellOrderWithCost': False,
'createOrder': True,
'createOrderWithTakeProfitAndStopLoss': False,
'createOrderWithTakeProfitAndStopLossWs': False,
'createReduceOnlyOrder': True,
'createStopLimitOrder': True,
'createStopMarketOrder': True,
'createStopOrder': True,
'fetchBalance': True,
'fetchBidsAsks': True,
'fetchBorrowInterest': False,
'fetchBorrowRate': False,
'fetchBorrowRateHistories': False,
'fetchBorrowRateHistory': False,
'fetchBorrowRates': False,
'fetchBorrowRatesPerSymbol': False,
'fetchClosedOrders': True,
'fetchCrossBorrowRate': False,
'fetchCrossBorrowRates': False,
'fetchCurrencies': True,
'fetchDepositAddress': False,
'fetchDeposits': True,
'fetchDepositsWithdrawals': False,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': True,
'fetchFundingHistory': False,
'fetchFundingInterval': False,
'fetchFundingIntervals': False,
'fetchFundingRate': False,
'fetchFundingRateHistory': False,
'fetchFundingRates': False,
'fetchGreeks': False,
'fetchIndexOHLCV': False,
'fetchIsolatedBorrowRate': False,
'fetchIsolatedBorrowRates': False,
'fetchIsolatedPositions': False,
'fetchLeverage': False,
'fetchLeverages': False,
'fetchLeverageTiers': False,
'fetchLiquidations': False,
'fetchLongShortRatio': False,
'fetchLongShortRatioHistory': False,
'fetchMarginAdjustmentHistory': False,
'fetchMarginMode': False,
'fetchMarginModes': False,
'fetchMarketLeverageTiers': False,
'fetchMarkets': True,
'fetchMarkOHLCV': False,
'fetchMarkPrices': False,
'fetchMyLiquidations': False,
'fetchMySettlementHistory': False,
'fetchMyTrades': True,
'fetchOHLCV': True,
'fetchOpenInterest': False,
'fetchOpenInterestHistory': False,
'fetchOpenInterests': False,
'fetchOpenOrders': True,
'fetchOption': False,
'fetchOptionChain': False,
'fetchOrder': True,
'fetchOrderBook': True,
'fetchOrders': False,
'fetchPosition': False,
'fetchPositionHistory': False,
'fetchPositionMode': False,
'fetchPositions': False,
'fetchPositionsHistory': False,
'fetchPositionsRisk': False,
'fetchPremiumIndexOHLCV': False,
'fetchSettlementHistory': False,
'fetchStatus': True,
'fetchTicker': True,
'fetchTickers': True,
'fetchTime': True,
'fetchTrades': True,
'fetchTradingFee': False,
'fetchTradingFees': False,
'fetchTransactionFees': False,
'fetchTransactions': False,
'fetchTransfers': True,
'fetchVolatilityHistory': False,
'fetchWithdrawals': True,
'reduceMargin': False,
'repayCrossMargin': False,
'repayIsolatedMargin': False,
'setLeverage': True,
'setMargin': True,
'setMarginMode': False,
'setPositionMode': False,
'transfer': True,
'withdraw': True,
},
'timeframes': {
'1m': '1m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1H',
'2h': '2H',
'4h': '4H',
'1d': '1D',
'1w': '1W',
},
'urls': {
'logo': 'https://github.com/user-attachments/assets/67abe346-1273-461a-bd7c-42fa32907c8e',
'api': {
'spot': 'https://www.bitrue.com/api',
'fapi': 'https://fapi.bitrue.com/fapi',
'dapi': 'https://fapi.bitrue.com/dapi',
'kline': 'https://www.bitrue.com/kline-api',
},
'www': 'https://www.bitrue.com',
'referral': 'https://www.bitrue.com/affiliate/landing?cn=600000&inviteCode=EZWETQE',
'doc': [
'https://github.com/Bitrue-exchange/bitrue-official-api-docs',
'https://www.bitrue.com/api-docs',
],
'fees': 'https://bitrue.zendesk.com/hc/en-001/articles/4405479952537',
},
# from spotV1PublicGetExchangeInfo:
# general 25000 weight in 1 minute per IP. = 416.66 per second a weight of 0.24 for 1
# orders 750 weight in 6 seconds per IP. = 125 per second a weight of 0.8 for 1
# orders 200 weight in 10 seconds per User. = 20 per second a weight of 5 for 1
# withdraw 3000 weight in 1 hour per User. = 0.833 per second a weight of 120 for 1
# withdraw 1000 weight in 1 day per User. = 0.011574 per second a weight of 8640 for 1
'api': {
'spot': {
'kline': {
'public': {
'get': {
'public.json': 0.24,
'public{currency}.json': 0.24,
},
},
},
'v1': {
'public': {
'get': {
'ping': 0.24,
'time': 0.24,
'exchangeInfo': 0.24,
'depth': {'cost': 1, 'byLimit': [[100, 0.24], [500, 1.2], [1000, 2.4]]},
'trades': 0.24,
'historicalTrades': 1.2,
'aggTrades': 0.24,
'ticker/24hr': {'cost': 0.24, 'noSymbol': 9.6},
'ticker/price': 0.24,
'ticker/bookTicker': 0.24,
'market/kline': 0.24,
},
},
'private': {
'get': {
'order': 5,
'openOrders': 5,
'allOrders': 25,
'account': 25,
'myTrades': 25,
'etf/net-value/{symbol}': 0.24,
'withdraw/history': 120,
'deposit/history': 120,
},
'post': {
'order': 5,
'withdraw/commit': 120,
},
'delete': {
'order': 5,
},
},
},
'v2': {
'private': {
'get': {
'myTrades': 1.2,
},
},
},
},
'fapi': {
'v1': {
'public': {
'get': {
'ping': 0.24,
'time': 0.24,
'contracts': 0.24,
'depth': 0.24,
'ticker': 0.24,
'klines': 0.24,
},
},
},
'v2': {
'private': {
'get': {
'myTrades': 5,
'openOrders': 5,
'order': 5,
'account': 5,
'leverageBracket': 5,
'commissionRate': 5,
'futures_transfer_history': 5,
'forceOrdersHistory': 5,
},
'post': {
'positionMargin': 5,
'level_edit': 5,
'cancel': 5,
'order': 25,
'allOpenOrders': 5,
'futures_transfer': 5,
},
},
},
},
'dapi': {
'v1': {
'public': {
'get': {
'ping': 0.24,
'time': 0.24,
'contracts': 0.24,
'depth': 0.24,
'ticker': 0.24,
'klines': 0.24,
},
},
},
'v2': {
'private': {
'get': {
'myTrades': 5,
'openOrders': 5,
'order': 5,
'account': 5,
'leverageBracket': 5,
'commissionRate': 5,
'futures_transfer_history': 5,
'forceOrdersHistory': 5,
},
'post': {
'positionMargin': 5,
'level_edit': 5,
'cancel': 5,
'order': 5,
'allOpenOrders': 5,
'futures_transfer': 5,
},
},
},
},
},
'fees': {
'trading': {
'feeSide': 'get',
'tierBased': False,
'percentage': True,
'taker': self.parse_number('0.00098'),
'maker': self.parse_number('0.00098'),
},
'future': {
'trading': {
'feeSide': 'quote',
'tierBased': True,
'percentage': True,
'taker': self.parse_number('0.000400'),
'maker': self.parse_number('0.000200'),
'tiers': {
'taker': [
[self.parse_number('0'), self.parse_number('0.000400')],
[self.parse_number('250'), self.parse_number('0.000400')],
[self.parse_number('2500'), self.parse_number('0.000350')],
[self.parse_number('7500'), self.parse_number('0.000320')],
[self.parse_number('22500'), self.parse_number('0.000300')],
[self.parse_number('50000'), self.parse_number('0.000270')],
[self.parse_number('100000'), self.parse_number('0.000250')],
[self.parse_number('200000'), self.parse_number('0.000220')],
[self.parse_number('400000'), self.parse_number('0.000200')],
[self.parse_number('750000'), self.parse_number('0.000170')],
],
'maker': [
[self.parse_number('0'), self.parse_number('0.000200')],
[self.parse_number('250'), self.parse_number('0.000160')],
[self.parse_number('2500'), self.parse_number('0.000140')],
[self.parse_number('7500'), self.parse_number('0.000120')],
[self.parse_number('22500'), self.parse_number('0.000100')],
[self.parse_number('50000'), self.parse_number('0.000080')],
[self.parse_number('100000'), self.parse_number('0.000060')],
[self.parse_number('200000'), self.parse_number('0.000040')],
[self.parse_number('400000'), self.parse_number('0.000020')],
[self.parse_number('750000'), self.parse_number('0')],
],
},
},
},
'delivery': {
'trading': {
'feeSide': 'base',
'tierBased': True,
'percentage': True,
'taker': self.parse_number('0.000500'),
'maker': self.parse_number('0.000100'),
'tiers': {
'taker': [
[self.parse_number('0'), self.parse_number('0.000500')],
[self.parse_number('250'), self.parse_number('0.000450')],
[self.parse_number('2500'), self.parse_number('0.000400')],
[self.parse_number('7500'), self.parse_number('0.000300')],
[self.parse_number('22500'), self.parse_number('0.000250')],
[self.parse_number('50000'), self.parse_number('0.000240')],
[self.parse_number('100000'), self.parse_number('0.000240')],
[self.parse_number('200000'), self.parse_number('0.000240')],
[self.parse_number('400000'), self.parse_number('0.000240')],
[self.parse_number('750000'), self.parse_number('0.000240')],
],
'maker': [
[self.parse_number('0'), self.parse_number('0.000100')],
[self.parse_number('250'), self.parse_number('0.000080')],
[self.parse_number('2500'), self.parse_number('0.000050')],
[self.parse_number('7500'), self.parse_number('0.0000030')],
[self.parse_number('22500'), self.parse_number('0')],
[self.parse_number('50000'), self.parse_number('-0.000050')],
[self.parse_number('100000'), self.parse_number('-0.000060')],
[self.parse_number('200000'), self.parse_number('-0.000070')],
[self.parse_number('400000'), self.parse_number('-0.000080')],
[self.parse_number('750000'), self.parse_number('-0.000090')],
],
},
},
},
},
# exchange-specific options
'options': {
'createMarketBuyOrderRequiresPrice': True,
'fetchMarkets': {
'types': ['spot', 'linear', 'inverse'],
},
# 'fetchTradesMethod': 'publicGetAggTrades', # publicGetTrades, publicGetHistoricalTrades
'fetchMyTradesMethod': 'v2PrivateGetMyTrades', # spotV1PrivateGetMyTrades
'hasAlreadyAuthenticatedSuccessfully': False,
'currencyToPrecisionRoundingMode': TRUNCATE,
'recvWindow': 5 * 1000, # 5 sec, binance default
'timeDifference': 0, # the difference between system clock and Binance clock
'adjustForTimeDifference': False, # controls the adjustment logic upon instantiation
'parseOrderToPrecision': False, # force amounts and costs in parseOrder to precision
'newOrderRespType': {
'market': 'FULL', # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
'limit': 'FULL', # we change it from 'ACK' by default to 'FULL'(returns immediately if limit is not hit)
},
'networks': {
'ERC20': 'ETH',
'TRC20': 'TRX',
'AETERNITY': 'Aeternity',
'AION': 'AION',
'ALGO': 'Algorand',
'ASK': 'ASK',
'ATOM': 'ATOM',
'AVAXC': 'AVAX C-Chain',
'BCH': 'BCH',
'BEP2': 'BEP2',
'BEP20': 'BEP20',
'Bitcoin': 'Bitcoin',
'BRP20': 'BRP20',
'ADA': 'Cardano',
'CASINOCOIN': 'CasinoCoin',
'CASINOCOIN-XRPL': 'CasinoCoin XRPL',
'CONTENTOS': 'Contentos',
'DASH': 'Dash',
'DECOIN': 'Decoin',
'DFI': 'DeFiChain',
'DGB': 'DGB',
'DIVI': 'Divi',
'DOGE': 'dogecoin',
'EOS': 'EOS',
'ETC': 'ETC',
'FILECOIN': 'Filecoin',
'FREETON': 'FREETON',
'HBAR': 'HBAR',
'HEDERA': 'Hedera Hashgraph',
'HRC20': 'HRC20',
'ICON': 'ICON',
'ICP': 'ICP',
'IGNIS': 'Ignis',
'INTERNETCOMPUTER': 'Internet Computer',
'IOTA': 'IOTA',
'KAVA': 'KAVA',
'KSM': 'KSM',
'LTC': 'LiteCoin',
'LUNA': 'Luna',
'MATIC': 'MATIC',
'MOBILECOIN': 'Mobile Coin',
'MONACOIN': 'MonaCoin',
'XMR': 'Monero',
'NEM': 'NEM',
'NEP5': 'NEP5',
'OMNI': 'OMNI',
'PAC': 'PAC',
'DOT': 'Polkadot',
'RAVEN': 'Ravencoin',
'SAFEX': 'Safex',
'SOL': 'SOLANA',
'SGB': 'Songbird',
'XML': 'Stellar Lumens',
'XYM': 'Symbol',
'XTZ': 'Tezos',
'theta': 'theta',
'THETA': 'THETA',
'VECHAIN': 'VeChain',
'WANCHAIN': 'Wanchain',
'XINFIN': 'XinFin Network',
'XRP': 'XRP',
'XRPL': 'XRPL',
'ZIL': 'ZIL',
},
'defaultType': 'spot',
'timeframes': {
'spot': {
'1m': '1m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1H',
'2h': '2H',
'4h': '4H',
'12h': '12H',
'1d': '1D',
'1w': '1W',
},
'future': {
'1m': '1min',
'5m': '5min',
'15m': '15min',
'30m': '30min',
'1h': '1h',
'1d': '1day',
'1w': '1week',
'1M': '1month',
},
},
'accountsByType': {
'spot': 'wallet',
'future': 'contract',
'swap': 'contract',
'funding': 'wallet',
'fund': 'wallet',
'contract': 'contract',
},
},
'commonCurrencies': {
'MIM': 'MIM Swarm',
},
'precisionMode': TICK_SIZE,
'features': {
'default': {
'sandbox': False,
'createOrder': {
'marginMode': False,
'triggerPrice': True,
'triggerPriceType': None,
'triggerDirection': None,
'stopLossPrice': False, # todo
'takeProfitPrice': False, # todo
'attachedStopLossTakeProfit': None,
'timeInForce': {
'IOC': True,
'FOK': True,
'PO': True,
'GTD': False,
},
'hedged': False,
'trailing': False,
'leverage': False,
'marketBuyRequiresPrice': True, # todo revise
'marketBuyByCost': True,
'selfTradePrevention': False,
'iceberg': True, # todo implement
},
'createOrders': None,
'fetchMyTrades': {
'marginMode': False,
'limit': 1000,
'daysBack': 100000,
'untilDays': 100000,
'symbolRequired': True,
},
'fetchOrder': {
'marginMode': False,
'trigger': False,
'trailing': False,
'symbolRequired': True,
},
'fetchOpenOrders': {
'marginMode': False,
'limit': None,
'trigger': False,
'trailing': False,
'symbolRequired': True,
},
'fetchOrders': None,
'fetchClosedOrders': {
'marginMode': False,
'limit': 1000,
'daysBack': 90,
'daysBackCanceled': 1,
'untilDays': 90,
'trigger': False,
'trailing': False,
'symbolRequired': True,
},
'fetchOHLCV': {
'limit': 1440,
},
},
'spot': {
'extends': 'default',
},
'forDerivatives': {
'extends': 'default',
'createOrder': {
'marginMode': True,
'leverage': True,
'marketBuyRequiresPrice': False,
'marketBuyByCost': False,
},
'fetchOHLCV': {
'limit': 300,
},
'fetchClosedOrders': None,
},
'swap': {
'linear': {
'extends': 'forDerivatives',
},
'inverse': {
'extends': 'forDerivatives',
},
},
'future': {
'linear': None,
'inverse': None,
},
},
'exceptions': {
'exact': {
'System is under maintenance.': OnMaintenance, # {"code":1,"msg":"System is under maintenance."}
'System abnormality': ExchangeError, # {"code":-1000,"msg":"System abnormality"}
'You are not authorized to execute self request.': PermissionDenied, # {"msg":"You are not authorized to execute self request."}
'API key does not exist': AuthenticationError,
'Order would trigger immediately.': OrderImmediatelyFillable,
'Stop price would trigger immediately.': OrderImmediatelyFillable, # {"code":-2010,"msg":"Stop price would trigger immediately."}
'Order would immediately match and take.': OrderImmediatelyFillable, # {"code":-2010,"msg":"Order would immediately match and take."}
'Account has insufficient balance for requested action.': InsufficientFunds,
'Rest API trading is not enabled.': ExchangeNotAvailable,
"You don't have permission.": PermissionDenied, # {"msg":"You don't have permission.","success":false}
'Market is closed.': ExchangeNotAvailable, # {"code":-1013,"msg":"Market is closed."}
'Too many requests. Please try again later.': DDoSProtection, # {"msg":"Too many requests. Please try again later.","success":false}
'-1000': ExchangeNotAvailable, # {"code":-1000,"msg":"An unknown error occured while processing the request."}
'-1001': ExchangeNotAvailable, # 'Internal error; unable to process your request. Please try again.'
'-1002': AuthenticationError, # 'You are not authorized to execute self request.'
'-1003': RateLimitExceeded, # {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."}
'-1013': InvalidOrder, # createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL
'-1015': RateLimitExceeded, # 'Too many new orders; current limit is %s orders per %s.'
'-1016': ExchangeNotAvailable, # 'This service is no longer available.',
'-1020': BadRequest, # 'This operation is not supported.'
'-1021': InvalidNonce, # 'your time is ahead of server'
'-1022': AuthenticationError, # {"code":-1022,"msg":"Signature for self request is not valid."}
'-1100': BadRequest, # createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'
'-1101': BadRequest, # Too many parameters; expected %s and received %s.
'-1102': BadRequest, # Param %s or %s must be sent, but both were empty # {"code":-1102,"msg":"timestamp IllegalArgumentException.","data":null}
'-1103': BadRequest, # An unknown parameter was sent.
'-1104': BadRequest, # Not all sent parameters were read, read 8 parameters but was sent 9
'-1105': BadRequest, # Parameter %s was empty.
'-1106': BadRequest, # Parameter %s sent when not required.
'-1111': BadRequest, # Precision is over the maximum defined for self asset.
'-1112': InvalidOrder, # No orders on book for symbol.
'-1114': BadRequest, # TimeInForce parameter sent when not required.
'-1115': BadRequest, # Invalid timeInForce.
'-1116': BadRequest, # Invalid orderType.
'-1117': BadRequest, # Invalid side.
'-1166': InvalidOrder, # {"code":"-1166","msg":"The leverage value of the order is inconsistent with the user contract configuration 5","data":null}
'-1118': BadRequest, # New client order ID was empty.
'-1119': BadRequest, # Original client order ID was empty.
'-1120': BadRequest, # Invalid interval.
'-1121': BadSymbol, # Invalid symbol.
'-1125': AuthenticationError, # This listenKey does not exist.
'-1127': BadRequest, # More than %s hours between startTime and endTime.
'-1128': BadRequest, # {"code":-1128,"msg":"Combination of optional parameters invalid."}
'-1130': BadRequest, # Data sent for paramter %s is not valid.
'-1131': BadRequest, # recvWindow must be less than 60000
'-1160': InvalidOrder, # {"code":"-1160","msg":"Minimum order amount 10","data":null}
'-1156': InvalidOrder, # {"code":"-1156","msg":"The number of closed positions exceeds the total number of positions","data":null}
'-2008': AuthenticationError, # {"code":-2008,"msg":"Invalid Api-Key ID."}
'-2010': ExchangeError, # generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc...
'-2011': OrderNotFound, # cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'
'-2013': OrderNotFound, # fetchOrder(1, 'BTC/USDT') -> 'Order does not exist'
'-2014': AuthenticationError, # {"code":-2014, "msg": "API-key format invalid."}
'-2015': AuthenticationError, # "Invalid API-key, IP, or permissions for action."
'-2017': InsufficientFunds, # {code":"-2017","msg":"Insufficient balance","data":null}
'-2019': InsufficientFunds, # {"code":-2019,"msg":"Margin is insufficient."}
'-3005': InsufficientFunds, # {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."}
'-3006': InsufficientFunds, # {"code":-3006,"msg":"Your borrow amount has exceed maximum borrow amount."}
'-3008': InsufficientFunds, # {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."}
'-3010': ExchangeError, # {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."}
'-3015': ExchangeError, # {"code":-3015,"msg":"Repay amount exceeds borrow amount."}
'-3022': AccountSuspended, # You account's trading is banned.
'-4028': BadRequest, # {"code":-4028,"msg":"Leverage 100 is not valid"}
'-3020': InsufficientFunds, # {"code":-3020,"msg":"Transfer out amount exceeds max amount."}
'-3041': InsufficientFunds, # {"code":-3041,"msg":"Balance is not enough"}
'-5013': InsufficientFunds, # Asset transfer failed: insufficient balance"
'-11008': InsufficientFunds, # {"code":-11008,"msg":"Exceeding the account's maximum borrowable limit."}
'-4051': InsufficientFunds, # {"code":-4051,"msg":"Isolated balance insufficient."}
},
'broad': {
'Insufficient account balance': InsufficientFunds, # {"code":-2010,"msg":"Insufficient account balance.","data":null}
'has no operation privilege': PermissionDenied,
'MAX_POSITION': InvalidOrder, # {"code":-2010,"msg":"Filter failure: MAX_POSITION"}
},
},
})
def nonce(self):
return self.milliseconds() - self.options['timeDifference']
async def fetch_status(self, params={}):
"""
the latest known information on the availability of the exchange API
https://github.com/Bitrue-exchange/Spot-official-api-docs#test-connectivity
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
"""
response = await self.spotV1PublicGetPing(params)
#
# empty means working status.
#
# {}
#
keys = list(response.keys())
keysLength = len(keys)
formattedStatus = 'maintenance' if keysLength else 'ok'
return {
'status': formattedStatus,
'updated': None,
'eta': None,
'url': None,
'info': response,
}
async def fetch_time(self, params={}) -> Int:
"""
fetches the current integer timestamp in milliseconds from the exchange server
https://github.com/Bitrue-exchange/Spot-official-api-docs#check-server-time
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns int: the current integer timestamp in milliseconds from the exchange server
"""
response = await self.spotV1PublicGetTime(params)
#
# {
# "serverTime":1635467280514
# }
#
return self.safe_integer(response, 'serverTime')
async def fetch_currencies(self, params={}) -> Currencies:
"""
fetches all available currencies on an exchange
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: an associative dictionary of currencies
"""
response = await self.spotV1PublicGetExchangeInfo(params)
#
# {
# "timezone":"CTT",
# "serverTime":1635464889117,
# "rateLimits":[
# {"rateLimitType":"REQUESTS_WEIGHT","interval":"MINUTES","limit":6000},
# {"rateLimitType":"ORDERS","interval":"SECONDS","limit":150},
# {"rateLimitType":"ORDERS","interval":"DAYS","limit":288000},
# ],
# "exchangeFilters":[],
# "symbols":[
# {
# "symbol":"SHABTC",
# "status":"TRADING",
# "baseAsset":"sha",
# "baseAssetPrecision":0,
# "quoteAsset":"btc",
# "quotePrecision":10,
# "orderTypes":["MARKET","LIMIT"],
# "icebergAllowed":false,
# "filters":[
# {"filterType":"PRICE_FILTER","minPrice":"0.00000001349","maxPrice":"0.00000017537","priceScale":10},
# {"filterType":"LOT_SIZE","minQty":"1.0","minVal":"0.00020","maxQty":"1000000000","volumeScale":0},
# ],
# "defaultPrice":"0.0000006100",
# },
# ],
# "coins":[
# {
# "coin": "near",
# "coinFulName": "NEAR Protocol",
# "chains": ["BEP20",],
# "chainDetail": [
# {
# "chain": "BEP20",
# "enableWithdraw": True,
# "enableDeposit": True,
# "withdrawFee": "0.2000",
# "minWithdraw": "5.0000",
# "maxWithdraw": "1000000000000000.0000",
# },
# ],
# },
# ],
# }
#
result: dict = {}
coins = self.safe_list(response, 'coins', [])
for i in range(0, len(coins)):
currency = coins[i]
id = self.safe_string(currency, 'coin')
name = self.safe_string(currency, 'coinFulName')
code = self.safe_currency_code(id)
networkDetails = self.safe_list(currency, 'chainDetail', [])
networks: dict = {}
for j in range(0, len(networkDetails)):
entry = networkDetails[j]
networkId = self.safe_string(entry, 'chain')
network = self.network_id_to_code(networkId, code)
networks[network] = {
'info': entry,
'id': networkId,
'network': network,
'deposit': self.safe_bool(entry, 'enableDeposit'),
'withdraw': self.safe_bool(entry, 'enableWithdraw'),
'active': None,
'fee': self.safe_number(entry, 'withdrawFee'),
'precision': None,
'limits': {
'withdraw': {
'min': self.safe_number(entry, 'minWithdraw'),
'max': self.safe_number(entry, 'maxWithdraw'),
},
},
}
result[code] = self.safe_currency_structure({
'id': id,
'name': name,
'code': code,
'precision': None,
'info': currency,
'active': None,
'deposit': None,
'withdraw': None,
'networks': networks,
'fee': None,
'fees': None,
'type': 'crypto',
'limits': {
'withdraw': {
'min': None,
'max': None,
},
},
})
return result
async def fetch_markets(self, params={}) -> List[Market]:
"""
retrieves data on all markets for bitrue
https://github.com/Bitrue-exchange/Spot-official-api-docs#exchangeInfo_endpoint
https://www.bitrue.com/api-docs#current-open-contract
https://www.bitrue.com/api_docs_includes_file/delivery.html#current-open-contract
:param dict [params]: extra parameters specific to the exchange api endpoint
:returns dict[]: an array of objects representing market data
"""
promisesRaw = []
types = None
defaultTypes = ['spot', 'linear', 'inverse']
fetchMarketsOptions = self.safe_dict(self.options, 'fetchMarkets')
if fetchMarketsOptions is not None:
types = self.safe_list(fetchMarketsOptions, 'types', defaultTypes)
else:
# for backward-compatibility
types = self.safe_list(self.options, 'fetchMarkets', defaultTypes)
for i in range(0, len(types)):
marketType = types[i]
if marketType == 'spot':
promisesRaw.append(self.spotV1PublicGetExchangeInfo(params))
elif marketType == 'linear':
promisesRaw.append(self.fapiV1PublicGetContracts(params))
elif marketType == 'inverse':
promisesRaw.append(self.dapiV1PublicGetContracts(params))
else:
raise ExchangeError(self.id + ' fetchMarkets() self.options fetchMarkets "' + marketType + '" is not a supported market type')
promises = await asyncio.gather(*promisesRaw)
spotMarkets = self.safe_value(self.safe_value(promises, 0), 'symbols', [])
futureMarkets = self.safe_value(promises, 1)
deliveryMarkets = self.safe_value(promises, 2)
markets = spotMarkets
markets = self.array_concat(markets, futureMarkets)
markets = self.array_concat(markets, deliveryMarkets)
#
# spot
#
# {
# "timezone":"CTT",
# "serverTime":1635464889117,
# "rateLimits":[
# {"rateLimitType":"REQUESTS_WEIGHT","interval":"MINUTES","limit":6000},
# {"rateLimitType":"ORDERS","interval":"SECONDS","limit":150},
# {"rateLimitType":"ORDERS","interval":"DAYS","limit":288000},
# ],
# "exchangeFilters":[],
# "symbols":[
# {
# "symbol":"SHABTC",
# "status":"TRADING",
# "baseAsset":"sha",
# "baseAssetPrecision":0,
# "quoteAsset":"btc",
# "quotePrecision":10,
# "orderTypes":["MARKET","LIMIT"],
# "icebergAllowed":false,
# "filters":[
# {"filterType":"PRICE_FILTER","minPrice":"0.00000001349","maxPrice":"0.00000017537","priceScale":10},
# {"filterType":"LOT_SIZE","minQty":"1.0","minVal":"0.00020","maxQty":"1000000000","volumeScale":0},
# ],
# "defaultPrice":"0.0000006100",
# },
# ],
# "coins":[
# {
# "coin":"sbr",
# "coinFulName":"Saber",
# "enableWithdraw":true,
# "enableDeposit":true,
# "chains":["SOLANA"],
# "withdrawFee":"2.0",
# "minWithdraw":"5.0",
# "maxWithdraw":"1000000000000000",
# },
# ],
# }
#
# swap / delivery
#
# [
# {
# "symbol": "H-HT-USDT",
# "pricePrecision": 8,
# "side": 1,
# "maxMarketVolume": 100000,
# "multiplier": 6,
# "minOrderVolume": 1,
# "maxMarketMoney": 10000000,
# "type": "H", # E: perpetual contract, S: test contract, others are mixed contract
# "maxLimitVolume": 1000000,
# "maxValidOrder": 20,
# "multiplierCoin": "HT",
# "minOrderMoney": 0.001,
# "maxLimitMoney": 1000000,
# "status": 1
# }
# ]
#
if self.options['adjustForTimeDifference']:
await self.load_time_difference()
return self.parse_markets(markets)
def parse_market(self, market: dict) -> Market:
id = self.safe_string(market, 'symbol')
lowercaseId = self.safe_string_lower(market, 'symbol')
side = self.safe_integer(market, 'side') # 1 linear, 0 inverse, None spot
type = None
isLinear = None
isInverse = None
if side is None:
type = 'spot'
else:
type = 'swap'
isLinear = (side == 1)
isInverse = (side == 0)
isContract = (type != 'spot')
baseId = self.safe_string(market, 'baseAsset')
quoteId = self.safe_string(market, 'quoteAsset')
settleId = None
settle = None
if isContract:
symbolSplit = id.split('-')
baseId = self.safe_string(symbolSplit, 1)
quoteId = self.safe_string(symbolSplit, 2)
if isLinear:
settleId = quoteId
else:
settleId = baseId
settle = self.safe_currency_code(settleId)
base = self.safe_currency_code(baseId)
quote = self.safe_currency_code(quoteId)
symbol = base + '/' + quote
if settle is not None:
symbol += ':' + settle
filters = self.safe_list(market, 'filters', [])
filtersByType = self.index_by(filters, 'filterType')
status = self.safe_string(market, 'status')
priceFilter = self.safe_dict(filtersByType, 'PRICE_FILTER', {})
amountFilter = self.safe_dict(filtersByType, 'LOT_SIZE', {})
defaultPricePrecision = self.safe_string(market, 'pricePrecision')
defaultAmountPrecision = self.safe_string(market, 'quantityPrecision')
pricePrecision = self.safe_string(priceFilter, 'priceScale', defaultPricePrecision)
amountPrecision = self.safe_string(amountFilter, 'volumeScale', defaultAmountPrecision)
multiplier = self.safe_string(market, 'multiplier')
maxQuantity = self.safe_number(amountFilter, 'maxQty')
if maxQuantity is None:
maxQuantity = self.safe_number(market, 'maxValidOrder')
minCost = self.safe_number(amountFilter, 'minVal')
if minCost is None:
minCost = self.safe_number(market, 'minOrderMoney')
return {
'id': id,
'lowercaseId': lowercaseId,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': settleId,
'type': type,
'spot': (type == 'spot'),
'margin': False,
'swap': isContract,
'future': False,
'option': False,
'active': (status == 'TRADING'),
'contract': isContract,
'linear': isLinear,
'inverse': isInverse,
'contractSize': self.parse_number(Precise.string_abs(multiplier)),
'expiry': None,
'expiryDatetime': None,
'strike': None,
'optionType': None,
'precision': {
'amount': self.parse_number(self.parse_precision(amountPrecision)),
'price': self.parse_number(self.parse_precision(pricePrecision)),
},
'limits': {
'leverage': {
'min': None,
'max': None,
},
'amount': {
'min': self.safe_number(amountFilter, 'minQty'),
'max': maxQuantity,
},
'price': {
'min': self.safe_number(priceFilter, 'minPrice'),
'max': self.safe_number(priceFilter, 'maxPrice'),
},
'cost': {
'min': minCost,
'max': None,
},
},
'created': None,
'info': market,
}
def parse_balance(self, response) -> Balances:
#
# spot
#
# {
# "makerCommission":0,
# "takerCommission":0,
# "buyerCommission":0,
# "sellerCommission":0,
# "updateTime":null,
# "balances":[
# {"asset":"sbr","free":"0","locked":"0"},
# {"asset":"ksm","free":"0","locked":"0"},
# {"asset":"neo3s","free":"0","locked":"0"},
# ],
# "canTrade":false,
# "canWithdraw":false,
# "canDeposit":false
# }
#
# swap
#
# {
# "account":[
# {
# "marginCoin":"USDT",
# "coinPrecious":4,
# "accountNormal":1010.4043400372839856,
# "accountLock":2.9827889600000006,
# "partPositionNormal":0,
# "totalPositionNormal":0,
# "achievedAmount":0,
# "unrealizedAmount":0,
# "totalMarginRate":0,
# "totalEquity":1010.4043400372839856,
# "partEquity":0,
# "totalCost":0,
# "sumMarginRate":0,
# "sumOpenRealizedAmount":0,
# "canUseTrialFund":0,
# "sumMaintenanceMargin":null,
# "futureModel":null,
# "positionVos":[]
# }
# ]
# }
#
result: dict = {
'info': response,
}
timestamp = self.safe_integer(response, 'updateTime')
balances = self.safe_value_2(response, 'balances', 'account', [])
for i in range(0, len(balances)):
balance = balances[i]
currencyId = self.safe_string_2(balance, 'asset', 'marginCoin')
code = self.safe_currency_code(currencyId)
account = self.account()
account['free'] = self.safe_string_2(balance, 'free', 'accountNormal')
account['used'] = self.safe_string_2(balance, 'locked', 'accountLock')
result[code] = account
result['timestamp'] = timestamp
result['datetime'] = self.iso8601(timestamp)
return self.safe_balance(result)
async def fetch_balance(self, params={}) -> Balances:
"""
query for balance and get the amount of funds available for trading or funds locked in orders
https://github.com/Bitrue-exchange/Spot-official-api-docs#account-information-user_data
https://www.bitrue.com/api-docs#account-information-v2-user_data-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#account-information-v2-user_data-hmac-sha256
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.type]: 'future', 'delivery', 'spot', 'swap'
:param str [params.subType]: 'linear', 'inverse'
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
"""
await self.load_markets()
type = None
type, params = self.handle_market_type_and_params('fetchBalance', None, params)
subType = None
subType, params = self.handle_sub_type_and_params('fetchBalance', None, params)
response = None
result = None
if type == 'swap':
if subType is not None and subType == 'inverse':
response = await self.dapiV2PrivateGetAccount(params)
result = self.safe_dict(response, 'data', {})
#
# {
# "code":"0",
# "msg":"Success",
# "data":{
# "account":[
# {
# "marginCoin":"USD",
# "coinPrecious":4,
# "accountNormal":1010.4043400372839856,
# "accountLock":2.9827889600000006,
# "partPositionNormal":0,
# "totalPositionNormal":0,
# "achievedAmount":0,
# "unrealizedAmount":0,
# "totalMarginRate":0,
# "totalEquity":1010.4043400372839856,
# "partEquity":0,
# "totalCost":0,
# "sumMarginRate":0,
# "sumOpenRealizedAmount":0,
# "canUseTrialFund":0,
# "sumMaintenanceMargin":null,
# "futureModel":null,
# "positionVos":[]
# }
# ]
# }
# }
#
else:
response = await self.fapiV2PrivateGetAccount(params)
result = self.safe_dict(response, 'data', {})
#
# {
# "code":"0",
# "msg":"Success",
# "data":{
# "account":[
# {
# "marginCoin":"USDT",
# "coinPrecious":4,
# "accountNormal":1010.4043400372839856,
# "accountLock":2.9827889600000006,
# "partPositionNormal":0,
# "totalPositionNormal":0,
# "achievedAmount":0,
# "unrealizedAmount":0,
# "totalMarginRate":0,
# "totalEquity":1010.4043400372839856,
# "partEquity":0,
# "totalCost":0,
# "sumMarginRate":0,
# "sumOpenRealizedAmount":0,
# "canUseTrialFund":0,
# "sumMaintenanceMargin":null,
# "futureModel":null,
# "positionVos":[]
# }
# ]
# }
# }
#
else:
response = await self.spotV1PrivateGetAccount(params)
result = response
#
# {
# "makerCommission":0,
# "takerCommission":0,
# "buyerCommission":0,
# "sellerCommission":0,
# "updateTime":null,
# "balances":[
# {"asset":"sbr","free":"0","locked":"0"},
# {"asset":"ksm","free":"0","locked":"0"},
# {"asset":"neo3s","free":"0","locked":"0"},
# ],
# "canTrade":false,
# "canWithdraw":false,
# "canDeposit":false
# }
#
return self.parse_balance(result)
async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://github.com/Bitrue-exchange/Spot-official-api-docs#order-book
https://www.bitrue.com/api-docs#order-book
https://www.bitrue.com/api_docs_includes_file/delivery.html#order-book
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
market = self.market(symbol)
response = None
if market['swap']:
request: dict = {
'contractName': market['id'],
}
if limit is not None:
if limit > 100:
limit = 100
request['limit'] = limit # default 100, max 100, see https://www.bitrue.com/api-docs#order-book
if market['linear']:
response = await self.fapiV1PublicGetDepth(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV1PublicGetDepth(self.extend(request, params))
elif market['spot']:
request: dict = {
'symbol': market['id'],
}
if limit is not None:
if limit > 1000:
limit = 1000
request['limit'] = limit # default 100, max 1000, see https://github.com/Bitrue-exchange/bitrue-official-api-docs#order-book
response = await self.spotV1PublicGetDepth(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchOrderBook only support spot & swap markets')
#
# spot
#
# {
# "lastUpdateId":1635474910177,
# "bids":[
# ["61436.84","0.05",[]],
# ["61435.77","0.0124",[]],
# ["61434.88","0.012",[]],
# ],
# "asks":[
# ["61452.46","0.0001",[]],
# ["61452.47","0.0597",[]],
# ["61452.76","0.0713",[]],
# ]
# }
#
# swap
#
# {
# "asks": [[34916.5, 2582], [34916.6, 2193], [34916.7, 2629], [34916.8, 3478], [34916.9, 2718]],
# "bids": [[34916.4, 92065], [34916.3, 25703], [34916.2, 37259], [34916.1, 26446], [34916, 44456]],
# "time": 1699338305000
# }
#
timestamp = self.safe_integer_2(response, 'time', 'lastUpdateId')
orderbook = self.parse_order_book(response, symbol, timestamp)
orderbook['nonce'] = self.safe_integer(response, 'lastUpdateId')
return orderbook
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
#
# fetchBidsAsks
#
# {
# "symbol": "LTCBTC",
# "bidPrice": "4.00000000",
# "bidQty": "431.00000000",
# "askPrice": "4.00000200",
# "askQty": "9.00000000"
# }
#
# fetchTicker
#
# {
# "symbol": "BNBBTC",
# "priceChange": "0.000248",
# "priceChangePercent": "3.5500",
# "weightedAvgPrice": null,
# "prevClosePrice": null,
# "lastPrice": "0.007226",
# "lastQty": null,
# "bidPrice": "0.007208",
# "askPrice": "0.007240",
# "openPrice": "0.006978",
# "highPrice": "0.007295",
# "lowPrice": "0.006935",
# "volume": "11749.86",
# "quoteVolume": "84.1066211",
# "openTime": 0,
# "closeTime": 0,
# "firstId": 0,
# "lastId": 0,
# "count": 0
# }
#
symbol = self.safe_symbol(None, market)
last = self.safe_string_2(ticker, 'lastPrice', 'last')
timestamp = self.safe_integer(ticker, 'time')
percentage = None
if market['swap']:
percentage = Precise.string_mul(self.safe_string(ticker, 'rose'), '100')
else:
percentage = self.safe_string(ticker, 'priceChangePercent')
return self.safe_ticker({
'symbol': symbol,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'high': self.safe_string_2(ticker, 'highPrice', 'high'),
'low': self.safe_string_2(ticker, 'lowPrice', 'low'),
'bid': self.safe_string_2(ticker, 'bidPrice', 'buy'),
'bidVolume': self.safe_string(ticker, 'bidQty'),
'ask': self.safe_string_2(ticker, 'askPrice', 'sell'),
'askVolume': self.safe_string(ticker, 'askQty'),
'vwap': self.safe_string(ticker, 'weightedAvgPrice'),
'open': self.safe_string(ticker, 'openPrice'),
'close': last,
'last': last,
'previousClose': None,
'change': self.safe_string(ticker, 'priceChange'),
'percentage': percentage,
'average': None,
'baseVolume': self.safe_string_2(ticker, 'volume', 'vol'),
'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
'info': ticker,
}, market)
async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
"""
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
https://github.com/Bitrue-exchange/Spot-official-api-docs#24hr-ticker-price-change-statistics
https://www.bitrue.com/api-docs#ticker
https://www.bitrue.com/api_docs_includes_file/delivery.html#ticker
:param str symbol: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
market = self.market(symbol)
response = None
data = None
if market['swap']:
request: dict = {
'contractName': market['id'],
}
if market['linear']:
response = await self.fapiV1PublicGetTicker(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV1PublicGetTicker(self.extend(request, params))
data = response
elif market['spot']:
request: dict = {
'symbol': market['id'],
}
response = await self.spotV1PublicGetTicker24hr(self.extend(request, params))
data = self.safe_dict(response, 0, {})
else:
raise NotSupported(self.id + ' fetchTicker only support spot & swap markets')
#
# spot
#
# [{
# symbol: 'BTCUSDT',
# priceChange: '105.20',
# priceChangePercent: '0.3000',
# weightedAvgPrice: null,
# prevClosePrice: null,
# lastPrice: '34905.21',
# lastQty: null,
# bidPrice: '34905.21',
# askPrice: '34905.22',
# openPrice: '34800.01',
# highPrice: '35276.33',
# lowPrice: '34787.51',
# volume: '12549.6481',
# quoteVolume: '439390492.917',
# openTime: '0',
# closeTime: '0',
# firstId: '0',
# lastId: '0',
# count: '0'
# }]
#
# swap
#
# {
# "high": "35296",
# "vol": "779308354",
# "last": "34884.1",
# "low": "34806.7",
# "buy": 34883.9,
# "sell": 34884,
# "rose": "-0.0027957315",
# "time": 1699348013000
# }
#
return self.parse_ticker(data, market)
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
"""
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://www.bitrue.com/api_docs_includes_file/spot/index.html#kline-data
https://www.bitrue.com/api_docs_includes_file/futures/index.html#kline-candlestick-data
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents
:param int [since]: timestamp in ms of the earliest candle to fetch
:param int [limit]: the maximum amount of candles to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: the latest time in ms to fetch transfers for
:returns int[][]: A list of candles ordered, open, high, low, close, volume
"""
await self.load_markets()
market = self.market(symbol)
timeframes = self.safe_dict(self.options, 'timeframes', {})
response = None
data = None
if market['swap']:
timeframesFuture = self.safe_dict(timeframes, 'future', {})
request: dict = {
'contractName': market['id'],
# 1min / 5min / 15min / 30min / 1h / 1day / 1week / 1month
'interval': self.safe_string(timeframesFuture, timeframe, '1min'),
}
if limit is not None:
request['limit'] = limit
if market['linear']:
response = await self.fapiV1PublicGetKlines(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV1PublicGetKlines(self.extend(request, params))
data = response
elif market['spot']:
timeframesSpot = self.safe_dict(timeframes, 'spot', {})
request: dict = {
'symbol': market['id'],
# 1m / 5m / 15m / 30m / 1H / 2H / 4H / 12H / 1D / 1W
'scale': self.safe_string(timeframesSpot, timeframe, '1m'),
}
if limit is not None:
request['limit'] = limit
until = self.safe_integer(params, 'until')
if until is not None:
params = self.omit(params, 'until')
request['fromIdx'] = until
response = await self.spotV1PublicGetMarketKline(self.extend(request, params))
data = self.safe_list(response, 'data', [])
else:
raise NotSupported(self.id + ' fetchOHLCV only support spot & swap markets')
#
# spot
#
# {
# "symbol":"BTCUSDT",
# "scale":"KLINE_1MIN",
# "data":[
# {
# "i":"1660825020",
# "a":"93458.778",
# "v":"3.9774",
# "c":"23494.99",
# "h":"23509.63",
# "l":"23491.93",
# "o":"23508.34"
# }
# ]
# }
#
# swap
#
# [
# {
# "high": "35360.7",
# "vol": "110288",
# "low": "35347.9",
# "idx": 1699411680000,
# "close": "35347.9",
# "open": "35349.4"
# }
# ]
#
return self.parse_ohlcvs(data, market, timeframe, since, limit)
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
#
# spot
#
# {
# "i":"1660825020",
# "a":"93458.778",
# "v":"3.9774",
# "c":"23494.99",
# "h":"23509.63",
# "l":"23491.93",
# "o":"23508.34"
# }
#
# swap
#
# {
# "high": "35360.7",
# "vol": "110288",
# "low": "35347.9",
# "idx": 1699411680000,
# "close": "35347.9",
# "open": "35349.4"
# }
#
timestamp = self.safe_timestamp(ohlcv, 'i')
if timestamp is None:
timestamp = self.safe_integer(ohlcv, 'idx')
return [
timestamp,
self.safe_number_2(ohlcv, 'o', 'open'),
self.safe_number_2(ohlcv, 'h', 'high'),
self.safe_number_2(ohlcv, 'l', 'low'),
self.safe_number_2(ohlcv, 'c', 'close'),
self.safe_number_2(ohlcv, 'v', 'vol'),
]
async def fetch_bids_asks(self, symbols: Strings = None, params={}):
"""
fetches the bid and ask price and volume for multiple markets
https://github.com/Bitrue-exchange/Spot-official-api-docs#symbol-order-book-ticker
https://www.bitrue.com/api-docs#ticker
https://www.bitrue.com/api_docs_includes_file/delivery.html#ticker
:param str[]|None symbols: unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols, None, False)
first = self.safe_string(symbols, 0)
market = self.market(first)
response = None
if market['swap']:
request: dict = {
'contractName': market['id'],
}
if market['linear']:
response = await self.fapiV1PublicGetTicker(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV1PublicGetTicker(self.extend(request, params))
elif market['spot']:
request: dict = {
'symbol': market['id'],
}
response = await self.spotV1PublicGetTickerBookTicker(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchBidsAsks only support spot & swap markets')
#
# spot
#
# {
# "symbol": "LTCBTC",
# "bidPrice": "4.00000000",
# "bidQty": "431.00000000",
# "askPrice": "4.00000200",
# "askQty": "9.00000000"
# }
#
# swap
#
# {
# "high": "35296",
# "vol": "779308354",
# "last": "34884.1",
# "low": "34806.7",
# "buy": 34883.9,
# "sell": 34884,
# "rose": "-0.0027957315",
# "time": 1699348013000
# }
#
data: dict = {}
data[market['id']] = response
return self.parse_tickers(data, symbols)
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
"""
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
https://github.com/Bitrue-exchange/Spot-official-api-docs#24hr-ticker-price-change-statistics
https://www.bitrue.com/api-docs#ticker
https://www.bitrue.com/api_docs_includes_file/delivery.html#ticker
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
await self.load_markets()
symbols = self.market_symbols(symbols)
response = None
data = None
request: dict = {}
type = None
if symbols is not None:
first = self.safe_string(symbols, 0)
market = self.market(first)
if market['swap']:
raise NotSupported(self.id + ' fetchTickers does not support swap markets, please use fetchTicker instead')
elif market['spot']:
response = await self.spotV1PublicGetTicker24hr(self.extend(request, params))
data = response
else:
raise NotSupported(self.id + ' fetchTickers only support spot & swap markets')
else:
type, params = self.handle_market_type_and_params('fetchTickers', None, params)
if type != 'spot':
raise NotSupported(self.id + ' fetchTickers only support spot when symbols are not proved')
response = await self.spotV1PublicGetTicker24hr(self.extend(request, params))
data = response
#
# spot
#
# [{
# symbol: 'BTCUSDT',
# priceChange: '105.20',
# priceChangePercent: '0.3000',
# weightedAvgPrice: null,
# prevClosePrice: null,
# lastPrice: '34905.21',
# lastQty: null,
# bidPrice: '34905.21',
# askPrice: '34905.22',
# openPrice: '34800.01',
# highPrice: '35276.33',
# lowPrice: '34787.51',
# volume: '12549.6481',
# quoteVolume: '439390492.917',
# openTime: '0',
# closeTime: '0',
# firstId: '0',
# lastId: '0',
# count: '0'
# }]
#
# swap
#
# {
# "high": "35296",
# "vol": "779308354",
# "last": "34884.1",
# "low": "34806.7",
# "buy": 34883.9,
# "sell": 34884,
# "rose": "-0.0027957315",
# "time": 1699348013000
# }
#
# the exchange returns market ids with an underscore from the tickers endpoint
# the market ids do not have an underscore, so it has to be removed
# https://github.com/ccxt/ccxt/issues/13856
tickers: dict = {}
for i in range(0, len(data)):
ticker = self.safe_dict(data, i, {})
market = self.safe_market(self.safe_string(ticker, 'symbol'))
tickers[market['id']] = ticker
return self.parse_tickers(tickers, symbols)
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
#
# fetchTrades
#
# {
# "id": 28457,
# "price": "4.00000100",
# "qty": "12.00000000",
# "time": 1499865549590, # Actual timestamp of trade
# "isBuyerMaker": True,
# "isBestMatch": True
# }
#
# fetchTrades - spot
#
# {
# "symbol":"USDCUSDT",
# "id":20725156,
# "orderId":2880918576,
# "origClientOrderId":null,
# "price":"0.9996000000000000",
# "qty":"100.0000000000000000",
# "commission":null,
# "commissionAssert":null,
# "time":1635558511000,
# "isBuyer":false,
# "isMaker":false,
# "isBestMatch":true
# }
#
# fetchTrades - future
#
# {
# "tradeId":12,
# "price":0.9,
# "qty":1,
# "amount":9,
# "contractName":"E-SAND-USDT",
# "side":"BUY",
# "fee":"0.0018",
# "bidId":1558124009467904992,
# "askId":1558124043827644908,
# "bidUserId":10294,
# "askUserId":10467,
# "isBuyer":true,
# "isMaker":true,
# "ctime":1678426306000
# }
#
timestamp = self.safe_integer_2(trade, 'ctime', 'time')
priceString = self.safe_string(trade, 'price')
amountString = self.safe_string(trade, 'qty')
marketId = self.safe_string_2(trade, 'symbol', 'contractName')
symbol = self.safe_symbol(marketId, market)
orderId = self.safe_string(trade, 'orderId')
id = self.safe_string_2(trade, 'id', 'tradeId')
side = None
buyerMaker = self.safe_bool(trade, 'isBuyerMaker') # ignore "m" until Bitrue fixes api
isBuyer = self.safe_bool(trade, 'isBuyer')
if buyerMaker is not None:
side = 'sell' if buyerMaker else 'buy'
if isBuyer is not None:
side = 'buy' if isBuyer else 'sell' # self is a True side
fee = None
if 'commission' in trade:
fee = {
'cost': self.safe_string_2(trade, 'commission', 'fee'),
'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAssert')),
}
takerOrMaker = None
isMaker = self.safe_bool(trade, 'isMaker')
if isMaker is not None:
takerOrMaker = 'maker' if isMaker else 'taker'
return self.safe_trade({
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': symbol,
'id': id,
'order': orderId,
'type': None,
'side': side,
'takerOrMaker': takerOrMaker,
'price': priceString,
'amount': amountString,
'cost': None,
'fee': fee,
}, market)
async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
get the list of most recent trades for a particular symbol
https://github.com/Bitrue-exchange/Spot-official-api-docs#recent-trades-list
:param str symbol: unified symbol of the market to fetch trades for
:param int [since]: timestamp in ms of the earliest trade to fetch
:param int [limit]: the maximum amount of trades to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
"""
await self.load_markets()
market = self.market(symbol)
response = None
if market['spot']:
request: dict = {
'symbol': market['id'],
# 'limit': 100, # default 100, max = 1000
}
if limit is not None:
request['limit'] = limit # default 100, max 1000
response = await self.spotV1PublicGetTrades(self.extend(request, params))
else:
raise NotSupported(self.id + ' fetchTrades only support spot markets')
#
# spot
#
# [
# {
# "id": 28457,
# "price": "4.00000100",
# "qty": "12.00000000",
# "time": 1499865549590,
# "isBuyerMaker": True,
# "isBestMatch": True
# }
# ]
#
return self.parse_trades(response, market, since, limit)
def parse_order_status(self, status: Str):
statuses: dict = {
'INIT': 'open',
'PENDING_CREATE': 'open',
'NEW': 'open',
'PARTIALLY_FILLED': 'open',
'FILLED': 'closed',
'CANCELED': 'canceled',
'PENDING_CANCEL': 'canceling', # currently unused
'REJECTED': 'rejected',
'EXPIRED': 'expired',
}
return self.safe_string(statuses, status, status)
def parse_order(self, order: dict, market: Market = None) -> Order:
#
# createOrder - spot
#
# {
# "symbol":"USDCUSDT",
# "orderId":2878854881,
# "clientOrderId":"",
# "transactTime":1635551031276
# }
#
# createOrder - future
#
# {
# "orderId":1690615676032452985,
# }
#
# fetchOrders - spot
#
# {
# "symbol":"USDCUSDT",
# "orderId":"2878854881",
# "clientOrderId":"",
# "price":"1.1000000000000000",
# "origQty":"100.0000000000000000",
# "executedQty":"0.0000000000000000",
# "cummulativeQuoteQty":"0.0000000000000000",
# "status":"NEW",
# "timeInForce":"",
# "type":"LIMIT",
# "side":"SELL",
# "stopPrice":"",
# "icebergQty":"",
# "time":1635551031000,
# "updateTime":1635551031000,
# "isWorking":false
# }
#
# fetchOrders - future
#
# {
# "orderId":1917641,
# "price":100,
# "origQty":10,
# "origAmount":10,
# "executedQty":1,
# "avgPrice":10000,
# "status":"INIT",
# "type":"LIMIT",
# "side":"BUY",
# "action":"OPEN",
# "transactTime":1686716571425
# "clientOrderId":4949299210
# }
#
status = self.parse_order_status(self.safe_string_2(order, 'status', 'orderStatus'))
marketId = self.safe_string(order, 'symbol')
symbol = self.safe_symbol(marketId, market)
filled = self.safe_string(order, 'executedQty')
timestamp = None
lastTradeTimestamp = None
if 'time' in order:
timestamp = self.safe_integer(order, 'time')
elif 'transactTime' in order:
timestamp = self.safe_integer(order, 'transactTime')
elif 'updateTime' in order:
if status == 'open':
if Precise.string_gt(filled, '0'):
lastTradeTimestamp = self.safe_integer(order, 'updateTime')
else:
timestamp = self.safe_integer(order, 'updateTime')
average = self.safe_string(order, 'avgPrice')
price = self.safe_string(order, 'price')
amount = self.safe_string(order, 'origQty')
# - Spot/Margin market: cummulativeQuoteQty
# - Futures market: cumQuote.
# Note self is not the actual cost, since Binance futures uses leverage to calculate margins.
cost = self.safe_string_2(order, 'cummulativeQuoteQty', 'cumQuote')
id = self.safe_string(order, 'orderId')
type = self.safe_string_lower(order, 'type')
side = self.safe_string_lower(order, 'side')
fills = self.safe_list(order, 'fills', [])
clientOrderId = self.safe_string(order, 'clientOrderId')
timeInForce = self.safe_string(order, 'timeInForce')
postOnly = (type == 'limit_maker') or (timeInForce == 'GTX') or (type == 'post_only')
if type == 'limit_maker':
type = 'limit'
triggerPrice = self.parse_number(self.omit_zero(self.safe_string(order, 'stopPrice')))
return self.safe_order({
'info': order,
'id': id,
'clientOrderId': clientOrderId,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'postOnly': postOnly,
'side': side,
'price': price,
'triggerPrice': triggerPrice,
'amount': amount,
'cost': cost,
'average': average,
'filled': filled,
'remaining': None,
'status': status,
'fee': None,
'trades': fills,
}, market)
async def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
"""
create a market buy order by providing the symbol and cost
https://www.bitrue.com/api-docs#new-order-trade-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#new-order-trade-hmac-sha256
: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>`
"""
await self.load_markets()
market = self.market(symbol)
if not market['swap']:
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports swap orders only')
params['createMarketBuyOrderRequiresPrice'] = False
return await self.create_order(symbol, 'market', 'buy', cost, None, params)
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
"""
create a trade order
https://www.bitrue.com/api_docs_includes_file/spot/index.html#new-order-trade
https://www.bitrue.com/api_docs_includes_file/futures/index.html#new-order-trade-hmac-sha256
:param str symbol: unified symbol of the market to create an order in
:param str type: 'market' or 'limit'
:param str side: 'buy' or 'sell'
:param float amount: how much of currency you want to trade in units of base currency
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
:param dict [params]: extra parameters specific to the exchange API endpoint
:param float [params.triggerPrice]: *spot only* the price at which a trigger order is triggered at
:param str [params.clientOrderId]: a unique id for the order, automatically generated if not sent
:param decimal [params.leverage]: in future order, the leverage value of the order should consistent with the user contract configuration, default is 1
:param str [params.timeInForce]: 'fok', 'ioc' or 'po'
:param bool [params.postOnly]: default False
:param bool [params.reduceOnly]: default False
EXCHANGE SPECIFIC PARAMETERS
:param decimal [params.icebergQty]:
:param long [params.recvWindow]:
:param float [params.cost]: *swap market buy only* the quote quantity that can be used alternative for the amount
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
await self.load_markets()
market = self.market(symbol)
response = None
data = None
uppercaseType = type.upper()
request: dict = {
'side': side.upper(),
'type': uppercaseType,
# 'timeInForce': '',
# 'price': self.price_to_precision(symbol, price),
# 'newClientOrderId': clientOrderId, # automatically generated if not sent
# 'stopPrice': self.price_to_precision(symbol, 'stopPrice'),
# 'icebergQty': self.amount_to_precision(symbol, icebergQty),
}
if uppercaseType == 'LIMIT':
if price is None:
raise InvalidOrder(self.id + ' createOrder() requires a price argument')
request['price'] = self.price_to_precision(symbol, price)
if market['swap']:
isMarket = uppercaseType == 'MARKET'
timeInForce = self.safe_string_lower(params, 'timeInForce')
postOnly = self.is_post_only(isMarket, None, params)
if postOnly:
request['type'] = 'POST_ONLY'
elif timeInForce == 'fok':
request['type'] = 'FOK'
elif timeInForce == 'ioc':
request['type'] = 'IOC'
request['contractName'] = market['id']
createMarketBuyOrderRequiresPrice = True
createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
if isMarket and (side == 'buy') and createMarketBuyOrderRequiresPrice:
cost = self.safe_string(params, 'cost')
params = self.omit(params, 'cost')
if price is None and cost is None:
raise InvalidOrder(self.id + ' createOrder() requires the price argument with swap market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options["createMarketBuyOrderRequiresPrice"] = False to supply the cost in the amount argument(the exchange-specific behaviour)')
else:
amountString = self.number_to_string(amount)
priceString = self.number_to_string(price)
quoteAmount = Precise.string_mul(amountString, priceString)
requestAmount = cost if (cost is not None) else quoteAmount
request['amount'] = self.cost_to_precision(symbol, requestAmount)
request['volume'] = self.cost_to_precision(symbol, requestAmount)
else:
request['amount'] = self.parse_to_numeric(amount)
request['volume'] = self.parse_to_numeric(amount)
request['positionType'] = 1
reduceOnly = self.safe_value_2(params, 'reduceOnly', 'reduce_only')
request['open'] = 'CLOSE' if reduceOnly else 'OPEN'
leverage = self.safe_string(params, 'leverage', '1')
request['leverage'] = self.parse_to_numeric(leverage)
params = self.omit(params, ['leverage', 'reduceOnly', 'reduce_only', 'timeInForce'])
if market['linear']:
response = await self.fapiV2PrivatePostOrder(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivatePostOrder(self.extend(request, params))
data = self.safe_dict(response, 'data', {})
elif market['spot']:
request['symbol'] = market['id']
request['quantity'] = self.amount_to_precision(symbol, amount)
validOrderTypes = self.safe_value(market['info'], 'orderTypes')
if not self.in_array(uppercaseType, validOrderTypes):
raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type in market ' + symbol)
clientOrderId = self.safe_string_2(params, 'newClientOrderId', 'clientOrderId')
if clientOrderId is not None:
params = self.omit(params, ['newClientOrderId', 'clientOrderId'])
request['newClientOrderId'] = clientOrderId
triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
if triggerPrice is not None:
params = self.omit(params, ['triggerPrice', 'stopPrice'])
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
response = await self.spotV1PrivatePostOrder(self.extend(request, params))
data = response
else:
raise NotSupported(self.id + ' createOrder only support spot & swap markets')
#
# spot
#
# {
# "symbol": "BTCUSDT",
# "orderId": 307650651173648896,
# "orderIdStr": "307650651173648896",
# "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
# "transactTime": 1507725176595
# }
#
# swap
#
# {
# "code": "0",
# "msg": "Success",
# "data": {
# "orderId": 1690615676032452985
# }
# }
#
return self.parse_order(data, market)
async def fetch_order(self, id: str, symbol: Str = None, params={}):
"""
fetches information on an order made by the user
https://www.bitrue.com/api_docs_includes_file/spot/index.html#query-order-user_data
https://www.bitrue.com/api_docs_includes_file/futures/index.html#query-order-user_data-hmac-sha256
:param str id: the order id
:param str symbol: unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
origClientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
params = self.omit(params, ['origClientOrderId', 'clientOrderId'])
response = None
data = None
request: dict = {}
if origClientOrderId is None:
request['orderId'] = id
else:
if market['swap']:
request['clientOrderId'] = origClientOrderId
else:
request['origClientOrderId'] = origClientOrderId
if market['swap']:
request['contractName'] = market['id']
if market['linear']:
response = await self.fapiV2PrivateGetOrder(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivateGetOrder(self.extend(request, params))
data = self.safe_dict(response, 'data', {})
elif market['spot']:
request['orderId'] = id # spot market id is mandatory
request['symbol'] = market['id']
response = await self.spotV1PrivateGetOrder(self.extend(request, params))
data = response
else:
raise NotSupported(self.id + ' fetchOrder only support spot & swap markets')
#
# spot
#
# {
# "symbol": "LTCBTC",
# "orderId": 1,
# "clientOrderId": "myOrder1",
# "price": "0.1",
# "origQty": "1.0",
# "executedQty": "0.0",
# "cummulativeQuoteQty": "0.0",
# "status": "NEW",
# "timeInForce": "GTC",
# "type": "LIMIT",
# "side": "BUY",
# "stopPrice": "0.0",
# "icebergQty": "0.0",
# "time": 1499827319559,
# "updateTime": 1499827319559,
# "isWorking": True
# }
#
# swap
#
# {
# "code":0,
# "msg":"success",
# "data":{
# "orderId":1917641,
# "price":100,
# "origQty":10,
# "origAmount":10,
# "executedQty":1,
# "avgPrice":10000,
# "status":"INIT",
# "type":"LIMIT",
# "side":"BUY",
# "action":"OPEN",
# "transactTime":1686716571425
# "clientOrderId":4949299210
# }
# }
#
return self.parse_order(data, market)
async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetches information on multiple closed orders made by the user
https://www.bitrue.com/api_docs_includes_file/spot/index.html#all-orders-user_data
:param str symbol: unified market symbol of the market orders were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
if not market['spot']:
raise NotSupported(self.id + ' fetchClosedOrders only support spot markets')
request: dict = {
'symbol': market['id'],
# 'orderId': 123445, # long
# 'startTime': since,
# 'endTime': self.milliseconds(),
# 'limit': limit, # default 100, max 1000
}
if since is not None:
request['startTime'] = since
if limit is not None:
request['limit'] = limit # default 100, max 1000
response = await self.spotV1PrivateGetAllOrders(self.extend(request, params))
#
# [
# {
# "symbol": "LTCBTC",
# "orderId": 1,
# "clientOrderId": "myOrder1",
# "price": "0.1",
# "origQty": "1.0",
# "executedQty": "0.0",
# "cummulativeQuoteQty": "0.0",
# "status": "NEW",
# "timeInForce": "GTC",
# "type": "LIMIT",
# "side": "BUY",
# "stopPrice": "0.0",
# "icebergQty": "0.0",
# "time": 1499827319559,
# "updateTime": 1499827319559,
# "isWorking": True
# }
# ]
#
return self.parse_orders(response, market, since, limit)
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
fetch all unfilled currently open orders
https://www.bitrue.com/api_docs_includes_file/spot/index.html#current-open-orders-user_data
https://www.bitrue.com/api_docs_includes_file/futures/index.html#cancel-all-open-orders-trade-hmac-sha256
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch open orders for
:param int [limit]: the maximum number of open order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
response = None
data = None
request: dict = {}
if market['swap']:
request['contractName'] = market['id']
if market['linear']:
response = await self.fapiV2PrivateGetOpenOrders(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivateGetOpenOrders(self.extend(request, params))
data = self.safe_list(response, 'data', [])
elif market['spot']:
request['symbol'] = market['id']
response = await self.spotV1PrivateGetOpenOrders(self.extend(request, params))
data = response
else:
raise NotSupported(self.id + ' fetchOpenOrders only support spot & swap markets')
#
# spot
#
# [
# {
# "symbol":"USDCUSDT",
# "orderId":"2878854881",
# "clientOrderId":"",
# "price":"1.1000000000000000",
# "origQty":"100.0000000000000000",
# "executedQty":"0.0000000000000000",
# "cummulativeQuoteQty":"0.0000000000000000",
# "status":"NEW",
# "timeInForce":"",
# "type":"LIMIT",
# "side":"SELL",
# "stopPrice":"",
# "icebergQty":"",
# "time":1635551031000,
# "updateTime":1635551031000,
# "isWorking":false
# }
# ]
#
# swap
#
# {
# "code": "0",
# "msg": "Success",
# "data": [{
# "orderId": 1917641,
# "clientOrderId": "2488514315",
# "price": 100,
# "origQty": 10,
# "origAmount": 10,
# "executedQty": 1,
# "avgPrice": 12451,
# "status": "INIT",
# "type": "LIMIT",
# "side": "BUY",
# "action": "OPEN",
# "transactTime": 1686717303975
# }
# ]
# }
#
return self.parse_orders(data, market, since, limit)
async def cancel_order(self, id: str, symbol: Str = None, params={}):
"""
cancels an open order
https://github.com/Bitrue-exchange/Spot-official-api-docs#cancel-order-trade
https://www.bitrue.com/api-docs#cancel-order-trade-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#cancel-order-trade-hmac-sha256
:param str id: order id
:param str symbol: unified symbol of the market the order was made in
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
await self.load_markets()
market = self.market(symbol)
origClientOrderId = self.safe_value_2(params, 'origClientOrderId', 'clientOrderId')
params = self.omit(params, ['origClientOrderId', 'clientOrderId'])
response = None
data = None
request: dict = {}
if origClientOrderId is None:
request['orderId'] = id
else:
if market['swap']:
request['clientOrderId'] = origClientOrderId
else:
request['origClientOrderId'] = origClientOrderId
if market['swap']:
request['contractName'] = market['id']
if market['linear']:
response = await self.fapiV2PrivatePostCancel(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivatePostCancel(self.extend(request, params))
data = self.safe_dict(response, 'data', {})
elif market['spot']:
request['symbol'] = market['id']
response = await self.spotV1PrivateDeleteOrder(self.extend(request, params))
data = response
else:
raise NotSupported(self.id + ' cancelOrder only support spot & swap markets')
#
# spot
#
# {
# "symbol": "LTCBTC",
# "origClientOrderId": "myOrder1",
# "orderId": 1,
# "clientOrderId": "cancelMyOrder1"
# }
#
# swap
#
# {
# "code": "0",
# "msg": "Success",
# "data": {
# "orderId": 1690615847831143159
# }
# }
#
return self.parse_order(data, market)
async def cancel_all_orders(self, symbol: Str = None, params={}):
"""
cancel all open orders in a market
https://www.bitrue.com/api-docs#cancel-all-open-orders-trade-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#cancel-all-open-orders-trade-hmac-sha256
:param str symbol: unified market symbol of the market to cancel orders in
:param dict [params]: extra parameters specific to the exchange API endpoint
:param str [params.marginMode]: 'cross' or 'isolated', for spot margin trading
:returns dict[]: a list of `order structures <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
"""
await self.load_markets()
market = self.market(symbol)
response = None
data = None
if market['swap']:
request: dict = {
'contractName': market['id'],
}
if market['linear']:
response = await self.fapiV2PrivatePostAllOpenOrders(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivatePostAllOpenOrders(self.extend(request, params))
data = self.safe_list(response, 'data', [])
else:
raise NotSupported(self.id + ' cancelAllOrders only support future markets')
#
# swap
#
# {
# 'code': '0',
# 'msg': 'Success',
# 'data': null
# }
#
return self.parse_orders(data, market)
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
"""
fetch all trades made by the user
https://www.bitrue.com/api_docs_includes_file/spot/index.html#account-trade-list-user_data
https://www.bitrue.com/api_docs_includes_file/futures/index.html#account-trade-list-user_data-hmac-sha256
:param str symbol: unified market symbol
:param int [since]: the earliest time in ms to fetch trades for
:param int [limit]: the maximum number of trades structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
"""
await self.load_markets()
if symbol is None:
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
market = self.market(symbol)
response = None
data = None
request: dict = {}
if since is not None:
request['startTime'] = since
if limit is not None:
if limit > 1000:
limit = 1000
request['limit'] = limit
if market['swap']:
request['contractName'] = market['id']
if market['linear']:
response = await self.fapiV2PrivateGetMyTrades(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivateGetMyTrades(self.extend(request, params))
data = self.safe_list(response, 'data', [])
elif market['spot']:
request['symbol'] = market['id']
response = await self.spotV2PrivateGetMyTrades(self.extend(request, params))
data = response
else:
raise NotSupported(self.id + ' fetchMyTrades only support spot & swap markets')
#
# spot
#
# [
# {
# "symbol":"USDCUSDT",
# "id":20725156,
# "orderId":2880918576,
# "origClientOrderId":null,
# "price":"0.9996000000000000",
# "qty":"100.0000000000000000",
# "commission":null,
# "commissionAssert":null,
# "time":1635558511000,
# "isBuyer":false,
# "isMaker":false,
# "isBestMatch":true
# }
# ]
#
# swap
#
# {
# "code":"0",
# "msg":"Success",
# "data":[
# {
# "tradeId":12,
# "price":0.9,
# "qty":1,
# "amount":9,
# "contractName":"E-SAND-USDT",
# "side":"BUY",
# "fee":"0.0018",
# "bidId":1558124009467904992,
# "askId":1558124043827644908,
# "bidUserId":10294,
# "askUserId":10467,
# "isBuyer":true,
# "isMaker":true,
# "ctime":1678426306000
# }
# ]
# }
#
return self.parse_trades(data, market, since, limit)
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch all deposits made to an account
https://github.com/Bitrue-exchange/Spot-official-api-docs#deposit-history--withdraw_data
:param str code: unified currency code
:param int [since]: the earliest time in ms to fetch deposits for
:param int [limit]: the maximum number of deposits structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
if code is None:
raise ArgumentsRequired(self.id + ' fetchDeposits() requires a code argument')
await self.load_markets()
currency = self.currency(code)
request: dict = {
'coin': currency['id'],
'status': 1, # 0 init, 1 finished, default 0
# 'offset': 0,
# 'limit': limit, # default 10, max 1000
# 'startTime': since,
# 'endTime': self.milliseconds(),
}
if since is not None:
request['startTime'] = since
# request['endTime'] = self.sum(since, 7776000000)
if limit is not None:
request['limit'] = limit
response = await self.spotV1PrivateGetDepositHistory(self.extend(request, params))
#
# {
# "code":200,
# "msg":"succ",
# "data":[
# {
# "id":2659137,
# "symbol":"USDC",
# "amount":"200.0000000000000000",
# "fee":"0.0E-15",
# "createdAt":1635503169000,
# "updatedAt":1635503202000,
# "addressFrom":"0x2faf487a4414fe77e2327f0bf4ae2a264a776ad2",
# "addressTo":"0x190ceccb1f8bfbec1749180f0ba8922b488d865b",
# "txid":"0x9970aec41099ac385568859517308707bc7d716df8dabae7b52f5b17351c3ed0",
# "confirmations":5,
# "status":0,
# "tagType":null,
# },
# {
# "id":2659137,
# "symbol": "XRP",
# "amount": "20.0000000000000000",
# "fee": "0.0E-15",
# "createdAt": 1544669393000,
# "updatedAt": 1544669413000,
# "addressFrom": "",
# "addressTo": "raLPjTYeGezfdb6crXZzcC8RkLBEwbBHJ5_18113641",
# "txid": "515B23E1F9864D3AF7F5B4C4FCBED784BAE861854FAB95F4031922B6AAEFC7AC",
# "confirmations": 7,
# "status": 1,
# "tagType": "Tag"
# }
# ]
# }
#
data = self.safe_list(response, 'data', [])
return self.parse_transactions(data, currency, since, limit)
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
"""
fetch all withdrawals made from an account
https://github.com/Bitrue-exchange/Spot-official-api-docs#withdraw-history--withdraw_data
:param str code: unified currency code
:param int [since]: the earliest time in ms to fetch withdrawals for
:param int [limit]: the maximum number of withdrawals structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
if code is None:
raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires a code argument')
await self.load_markets()
currency = self.currency(code)
request: dict = {
'coin': currency['id'],
'status': 5, # 0 init, 5 finished, 6 canceled, default 0
# 'offset': 0,
# 'limit': limit, # default 10, max 1000
# 'startTime': since,
# 'endTime': self.milliseconds(),
}
if since is not None:
request['startTime'] = since
# request['endTime'] = self.sum(since, 7776000000)
if limit is not None:
request['limit'] = limit
response = await self.spotV1PrivateGetWithdrawHistory(self.extend(request, params))
#
# {
# "code": 200,
# "msg": "succ",
# "data": [
# {
# "id": 183745,
# "symbol": "usdt_erc20",
# "amount": "8.4000000000000000",
# "fee": "1.6000000000000000",
# "payAmount": "0.0000000000000000",
# "createdAt": 1595336441000,
# "updatedAt": 1595336576000,
# "addressFrom": "",
# "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83",
# "txid": "",
# "confirmations": 0,
# "status": 6,
# "tagType": null
# }
# ]
# }
#
data = self.safe_list(response, 'data', [])
return self.parse_transactions(data, currency)
def parse_transaction_status_by_type(self, status, type=None):
statusesByType: dict = {
'deposit': {
'0': 'pending',
'1': 'ok',
},
'withdrawal': {
'0': 'pending', # Email Sent
'5': 'ok', # Failure
'6': 'canceled',
},
}
statuses = self.safe_dict(statusesByType, type, {})
return self.safe_string(statuses, status, status)
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
#
# fetchDeposits
#
# {
# "symbol": "XRP",
# "amount": "261.3361000000000000",
# "fee": "0.0E-15",
# "createdAt": 1548816979000,
# "updatedAt": 1548816999000,
# "addressFrom": "",
# "addressTo": "raLPjTYeGezfdb6crXZzcC8RkLBEwbBHJ5_18113641",
# "txid": "86D6EB68A7A28938BCE06BD348F8C07DEF500C5F7FE92069EF8C0551CE0F2C7D",
# "confirmations": 8,
# "status": 1,
# "tagType": "Tag"
# },
# {
# "symbol": "XRP",
# "amount": "20.0000000000000000",
# "fee": "0.0E-15",
# "createdAt": 1544669393000,
# "updatedAt": 1544669413000,
# "addressFrom": "",
# "addressTo": "raLPjTYeGezfdb6crXZzcC8RkLBEwbBHJ5_18113641",
# "txid": "515B23E1F9864D3AF7F5B4C4FCBED784BAE861854FAB95F4031922B6AAEFC7AC",
# "confirmations": 7,
# "status": 1,
# "tagType": "Tag"
# }
#
# fetchWithdrawals
#
# {
# "id": 183745,
# "symbol": "usdt_erc20",
# "amount": "8.4000000000000000",
# "fee": "1.6000000000000000",
# "payAmount": "0.0000000000000000",
# "createdAt": 1595336441000,
# "updatedAt": 1595336576000,
# "addressFrom": "",
# "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83",
# "txid": "",
# "confirmations": 0,
# "status": 6,
# "tagType": null
# }
#
# withdraw
#
# {
# "msg": null,
# "amount": 1000,
# "fee": 1,
# "ctime": null,
# "coin": "usdt_erc20",
# "withdrawId": 1156423,
# "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83"
# }
#
id = self.safe_string_2(transaction, 'id', 'withdrawId')
tagType = self.safe_string(transaction, 'tagType')
addressTo = self.safe_string(transaction, 'addressTo')
addressFrom = self.safe_string(transaction, 'addressFrom')
tagTo = None
tagFrom = None
if tagType is not None:
if addressTo is not None:
parts = addressTo.split('_')
addressTo = self.safe_string(parts, 0)
tagTo = self.safe_string(parts, 1)
if addressFrom is not None:
parts = addressFrom.split('_')
addressFrom = self.safe_string(parts, 0)
tagFrom = self.safe_string(parts, 1)
txid = self.safe_string(transaction, 'txid')
timestamp = self.safe_integer(transaction, 'createdAt')
updated = self.safe_integer(transaction, 'updatedAt')
payAmount = ('payAmount' in transaction)
ctime = ('ctime' in transaction)
type = 'withdrawal' if (payAmount or ctime) else 'deposit'
status = self.parse_transaction_status_by_type(self.safe_string(transaction, 'status'), type)
amount = self.safe_number(transaction, 'amount')
network = None
currencyId = self.safe_string_2(transaction, 'symbol', 'coin')
if currencyId is not None:
parts = currencyId.split('_')
currencyId = self.safe_string(parts, 0)
networkId = self.safe_string(parts, 1)
if networkId is not None:
network = networkId.upper()
code = self.safe_currency_code(currencyId, currency)
feeCost = self.safe_number(transaction, 'fee')
fee = None
if feeCost is not None:
fee = {'currency': code, 'cost': feeCost}
return {
'info': transaction,
'id': id,
'txid': txid,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'network': network,
'address': addressTo,
'addressTo': addressTo,
'addressFrom': addressFrom,
'tag': tagTo,
'tagTo': tagTo,
'tagFrom': tagFrom,
'type': type,
'amount': amount,
'currency': code,
'status': status,
'updated': updated,
'internal': False,
'comment': None,
'fee': fee,
}
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
"""
make a withdrawal
https://github.com/Bitrue-exchange/Spot-official-api-docs#withdraw-commit--withdraw_data
:param str code: unified currency code
:param float amount: the amount to withdraw
:param str address: the address to withdraw to
:param str tag:
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
"""
tag, params = self.handle_withdraw_tag_and_params(tag, params)
self.check_address(address)
await self.load_markets()
currency = self.currency(code)
request: dict = {
'coin': currency['id'],
'amount': amount,
'addressTo': address,
# 'chainName': chainName, # 'ERC20', 'TRC20', 'SOL'
# 'addressMark': '', # mark of address
# 'addrType': '', # type of address
# 'tag': tag,
}
networkCode = None
networkCode, params = self.handle_network_code_and_params(params)
if networkCode is not None:
request['chainName'] = self.network_code_to_id(networkCode)
if tag is not None:
request['tag'] = tag
response = await self.spotV1PrivatePostWithdrawCommit(self.extend(request, params))
#
# {
# "code": 200,
# "msg": "succ",
# "data": {
# "msg": null,
# "amount": 1000,
# "fee": 1,
# "ctime": null,
# "coin": "usdt_erc20",
# "withdrawId": 1156423,
# "addressTo": "0x2edfae3878d7b6db70ce4abed177ab2636f60c83"
# }
# }
#
data = self.safe_dict(response, 'data', {})
return self.parse_transaction(data, currency)
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
#
# {
# "coin": "adx",
# "coinFulName": "Ambire AdEx",
# "chains": ["BSC"],
# "chainDetail": [[Object]]
# }
#
chainDetails = self.safe_list(fee, 'chainDetail', [])
chainDetailLength = len(chainDetails)
result: dict = {
'info': fee,
'withdraw': {
'fee': None,
'percentage': None,
},
'deposit': {
'fee': None,
'percentage': None,
},
'networks': {},
}
if chainDetailLength != 0:
for i in range(0, chainDetailLength):
chainDetail = chainDetails[i]
networkId = self.safe_string(chainDetail, 'chain')
currencyCode = self.safe_string(currency, 'code')
networkCode = self.network_id_to_code(networkId, currencyCode)
result['networks'][networkCode] = {
'deposit': {'fee': None, 'percentage': None},
'withdraw': {'fee': self.safe_number(chainDetail, 'withdrawFee'), 'percentage': False},
}
if chainDetailLength == 1:
result['withdraw']['fee'] = self.safe_number(chainDetail, 'withdrawFee')
result['withdraw']['percentage'] = False
return result
async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
"""
fetch deposit and withdraw fees
https://github.com/Bitrue-exchange/Spot-official-api-docs#exchangeInfo_endpoint
:param str[]|None codes: list of unified currency codes
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
"""
await self.load_markets()
response = await self.spotV1PublicGetExchangeInfo(params)
coins = self.safe_list(response, 'coins')
return self.parse_deposit_withdraw_fees(coins, codes, 'coin')
def parse_transfer(self, transfer, currency=None):
#
# fetchTransfers
#
# {
# 'transferType': 'wallet_to_contract',
# 'symbol': 'USDT',
# 'amount': 1.0,
# 'status': 1,
# 'ctime': 1685404575000
# }
#
# transfer
#
# {}
#
transferType = self.safe_string(transfer, 'transferType')
fromAccount = None
toAccount = None
if transferType is not None:
accountSplit = transferType.split('_to_')
fromAccount = self.safe_string(accountSplit, 0)
toAccount = self.safe_string(accountSplit, 1)
timestamp = self.safe_integer(transfer, 'ctime')
return {
'info': transfer,
'id': None,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'currency': self.safe_string(currency, 'code'),
'amount': self.safe_number(transfer, 'amount'),
'fromAccount': fromAccount,
'toAccount': toAccount,
'status': 'ok',
}
async def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[TransferEntry]:
"""
fetch a history of internal transfers made on an account
https://www.bitrue.com/api-docs#get-future-account-transfer-history-list-user_data-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#get-future-account-transfer-history-list-user_data-hmac-sha256
:param str code: unified currency code of the currency transferred
:param int [since]: the earliest time in ms to fetch transfers for
:param int [limit]: the maximum number of transfers structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:param int [params.until]: the latest time in ms to fetch transfers for
:param str [params.type]: transfer type wallet_to_contract or contract_to_wallet
:returns dict[]: a list of `transfer structures <https://github.com/ccxt/ccxt/wiki/Manual#transfer-structure>`
"""
await self.load_markets()
type = self.safe_string_2(params, 'type', 'transferType')
request: dict = {
'transferType': type,
}
currency = None
if code is not None:
currency = self.currency(code)
request['coinSymbol'] = currency['id']
if since is not None:
request['beginTime'] = since
if limit is not None:
if limit > 200:
limit = 200
request['limit'] = limit
until = self.safe_integer(params, 'until')
if until is not None:
params = self.omit(params, 'until')
request['endTime'] = until
response = await self.fapiV2PrivateGetFuturesTransferHistory(self.extend(request, params))
#
# {
# 'code': '0',
# 'msg': 'Success',
# 'data': [{
# 'transferType': 'wallet_to_contract',
# 'symbol': 'USDT',
# 'amount': 1.0,
# 'status': 1,
# 'ctime': 1685404575000
# }]
# }
#
data = self.safe_list(response, 'data', [])
return self.parse_transfers(data, currency, since, limit)
async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
"""
transfer currency internally between wallets on the same account
https://www.bitrue.com/api-docs#new-future-account-transfer-user_data-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#user-commission-rate-user_data-hmac-sha256
:param str code: unified currency code
:param float amount: amount to transfer
:param str fromAccount: account to transfer from
:param str toAccount: account to transfer to
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `transfer structure <https://github.com/ccxt/ccxt/wiki/Manual#transfer-structure>`
"""
await self.load_markets()
currency = self.currency(code)
accountTypes = self.safe_dict(self.options, 'accountsByType', {})
fromId = self.safe_string(accountTypes, fromAccount, fromAccount)
toId = self.safe_string(accountTypes, toAccount, toAccount)
request: dict = {
'coinSymbol': currency['id'],
'amount': self.currency_to_precision(code, amount),
'transferType': fromId + '_to_' + toId,
}
response = await self.fapiV2PrivatePostFuturesTransfer(self.extend(request, params))
#
# {
# 'code': '0',
# 'msg': 'Success',
# 'data': null
# }
#
data = self.safe_dict(response, 'data', {})
return self.parse_transfer(data, currency)
async def set_leverage(self, leverage: int, symbol: Str = None, params={}):
"""
set the level of leverage for a market
https://www.bitrue.com/api-docs#change-initial-leverage-trade-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#change-initial-leverage-trade-hmac-sha256
:param float leverage: the rate of leverage
:param str symbol: unified market symbol
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: response from the exchange
"""
if symbol is None:
raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
if (leverage < 1) or (leverage > 125):
raise BadRequest(self.id + ' leverage should be between 1 and 125')
await self.load_markets()
market = self.market(symbol)
response = None
request: dict = {
'contractName': market['id'],
'leverage': leverage,
}
if not market['swap']:
raise NotSupported(self.id + ' setLeverage only support swap markets')
if market['linear']:
response = await self.fapiV2PrivatePostLevelEdit(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivatePostLevelEdit(self.extend(request, params))
return response
def parse_margin_modification(self, data, market=None) -> MarginModification:
#
# setMargin
#
# {
# "code": 0,
# "msg": "success"
# "data": null
# }
#
return {
'info': data,
'symbol': market['symbol'],
'type': None,
'marginMode': 'isolated',
'amount': None,
'total': None,
'code': None,
'status': None,
'timestamp': None,
'datetime': None,
}
async def set_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
"""
Either adds or reduces margin in an isolated position in order to set the margin to a specific value
https://www.bitrue.com/api-docs#modify-isolated-position-margin-trade-hmac-sha256
https://www.bitrue.com/api_docs_includes_file/delivery.html#modify-isolated-position-margin-trade-hmac-sha256
:param str symbol: unified market symbol of the market to set margin in
:param float amount: the amount to set the margin to
:param dict [params]: parameters specific to the exchange API endpoint
:returns dict: A `margin structure <https://github.com/ccxt/ccxt/wiki/Manual#add-margin-structure>`
"""
await self.load_markets()
market = self.market(symbol)
if not market['swap']:
raise NotSupported(self.id + ' setMargin only support swap markets')
response = None
request: dict = {
'contractName': market['id'],
'amount': self.parse_to_numeric(amount),
}
if market['linear']:
response = await self.fapiV2PrivatePostPositionMargin(self.extend(request, params))
elif market['inverse']:
response = await self.dapiV2PrivatePostPositionMargin(self.extend(request, params))
#
# {
# "code": 0,
# "msg": "success"
# "data": null
# }
#
return self.parse_margin_modification(response, market)
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
type = self.safe_string(api, 0)
version = self.safe_string(api, 1)
access = self.safe_string(api, 2)
url = None
if (type == 'api' and version == 'kline') or (type == 'open' and path.find('listenKey') >= 0):
url = self.urls['api'][type]
else:
url = self.urls['api'][type] + '/' + version
url = url + '/' + self.implode_params(path, params)
params = self.omit(params, self.extract_params(path))
if access == 'private':
self.check_required_credentials()
recvWindow = self.safe_integer(self.options, 'recvWindow', 5000)
if type == 'spot' or type == 'open':
query = self.urlencode(self.extend({
'timestamp': self.nonce(),
'recvWindow': recvWindow,
}, params))
signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256)
query += '&' + 'signature=' + signature
headers = {
'X-MBX-APIKEY': self.apiKey,
}
if (method == 'GET') or (method == 'DELETE'):
url += '?' + query
else:
body = query
headers['Content-Type'] = 'application/x-www-form-urlencoded'
else:
timestamp = str(self.nonce())
signPath = None
if type == 'fapi':
signPath = '/fapi'
elif type == 'dapi':
signPath = '/dapi'
signPath = signPath + '/' + version + '/' + path
signMessage = timestamp + method + signPath
if method == 'GET':
keys = list(params.keys())
keysLength = len(keys)
if keysLength > 0:
signMessage += '?' + self.urlencode(params)
signature = self.hmac(self.encode(signMessage), self.encode(self.secret), hashlib.sha256)
headers = {
'X-CH-APIKEY': self.apiKey,
'X-CH-SIGN': signature,
'X-CH-TS': timestamp,
}
url += '?' + self.urlencode(params)
else:
query = self.extend({
'recvWindow': recvWindow,
}, params)
body = self.json(query)
signMessage += body
signature = self.hmac(self.encode(signMessage), self.encode(self.secret), hashlib.sha256)
headers = {
'Content-Type': 'application/json',
'X-CH-APIKEY': self.apiKey,
'X-CH-SIGN': signature,
'X-CH-TS': timestamp,
}
else:
if params:
url += '?' + self.urlencode(params)
return {'url': url, 'method': method, 'body': body, 'headers': headers}
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
if (code == 418) or (code == 429):
raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
# error response in a form: {"code": -1013, "msg": "Invalid quantity."}
# following block cointains legacy checks against message patterns in "msg" property
# will switch "code" checks eventually, when we know all of them
if code >= 400:
if body.find('Price * QTY is zero or less') >= 0:
raise InvalidOrder(self.id + ' order cost = amount * price is zero or less ' + body)
if body.find('LOT_SIZE') >= 0:
raise InvalidOrder(self.id + ' order amount should be evenly divisible by lot size ' + body)
if body.find('PRICE_FILTER') >= 0:
raise InvalidOrder(self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid float value in general, use self.price_to_precision(symbol, amount) ' + body)
if response is None:
return None # fallback to default error handler
# check success value for wapi endpoints
# response in format {'msg': 'The coin does not exist.', 'success': True/false}
success = self.safe_bool(response, 'success', True)
if not success:
messageInner = self.safe_string(response, 'msg')
parsedMessage = None
if messageInner is not None:
try:
parsedMessage = json.loads(messageInner)
except Exception as e:
# do nothing
parsedMessage = None
if parsedMessage is not None:
response = parsedMessage
message = self.safe_string(response, 'msg')
if message is not None:
self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message)
self.throw_broadly_matched_exception(self.exceptions['broad'], message, self.id + ' ' + message)
# checks against error codes
error = self.safe_string(response, 'code')
if error is not None:
# https://github.com/ccxt/ccxt/issues/6501
# https://github.com/ccxt/ccxt/issues/7742
if (error == '200') or Precise.string_equals(error, '0'):
return None
# a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
# despite that their message is very confusing, it is raised by Binance
# on a temporary ban, the API key is valid, but disabled for a while
if (error == '-2015') and self.options['hasAlreadyAuthenticatedSuccessfully']:
raise DDoSProtection(self.id + ' temporary banned: ' + body)
feedback = self.id + ' ' + body
self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
raise ExchangeError(feedback)
if not success:
raise ExchangeError(self.id + ' ' + body)
return None
def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
if ('noSymbol' in config) and not ('symbol' in params):
return config['noSymbol']
elif ('byLimit' in config) and ('limit' in params):
limit = params['limit']
byLimit = config['byLimit']
for i in range(0, len(byLimit)):
entry = byLimit[i]
if limit <= entry[0]:
return entry[1]
return self.safe_value(config, 'cost', 1)