3196 lines
138 KiB
Python
3196 lines
138 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
from ccxt.base.exchange import Exchange
|
|
from ccxt.abstract.bitrue import ImplicitAPI
|
|
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']
|
|
|
|
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 = 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,
|
|
}
|
|
|
|
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 = self.spotV1PublicGetTime(params)
|
|
#
|
|
# {
|
|
# "serverTime":1635467280514
|
|
# }
|
|
#
|
|
return self.safe_integer(response, 'serverTime')
|
|
|
|
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 = 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
|
|
|
|
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 = 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']:
|
|
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)
|
|
|
|
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>`
|
|
"""
|
|
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 = 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 = 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 = 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)
|
|
|
|
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
|
|
"""
|
|
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 = self.fapiV1PublicGetDepth(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = 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 = 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)
|
|
|
|
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>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
response = None
|
|
data = None
|
|
if market['swap']:
|
|
request: dict = {
|
|
'contractName': market['id'],
|
|
}
|
|
if market['linear']:
|
|
response = self.fapiV1PublicGetTicker(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = self.dapiV1PublicGetTicker(self.extend(request, params))
|
|
data = response
|
|
elif market['spot']:
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = 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)
|
|
|
|
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
|
|
"""
|
|
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 = self.fapiV1PublicGetKlines(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = 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 = 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'),
|
|
]
|
|
|
|
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>`
|
|
"""
|
|
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 = self.fapiV1PublicGetTicker(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = self.dapiV1PublicGetTicker(self.extend(request, params))
|
|
elif market['spot']:
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = 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)
|
|
|
|
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>`
|
|
"""
|
|
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 = 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 = 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)
|
|
|
|
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>`
|
|
"""
|
|
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 = 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)
|
|
|
|
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>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
if not market['swap']:
|
|
raise NotSupported(self.id + ' createMarketBuyOrderWithCost() supports swap orders only')
|
|
params['createMarketBuyOrderRequiresPrice'] = False
|
|
return self.create_order(symbol, 'market', 'buy', cost, None, params)
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://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>`
|
|
"""
|
|
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 = self.fapiV2PrivatePostOrder(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = 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 = 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)
|
|
|
|
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')
|
|
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 = self.fapiV2PrivateGetOrder(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = 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 = 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)
|
|
|
|
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')
|
|
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 = 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)
|
|
|
|
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')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
response = None
|
|
data = None
|
|
request: dict = {}
|
|
if market['swap']:
|
|
request['contractName'] = market['id']
|
|
if market['linear']:
|
|
response = self.fapiV2PrivateGetOpenOrders(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = self.dapiV2PrivateGetOpenOrders(self.extend(request, params))
|
|
data = self.safe_list(response, 'data', [])
|
|
elif market['spot']:
|
|
request['symbol'] = market['id']
|
|
response = 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)
|
|
|
|
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')
|
|
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 = self.fapiV2PrivatePostCancel(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = self.dapiV2PrivatePostCancel(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
elif market['spot']:
|
|
request['symbol'] = market['id']
|
|
response = 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)
|
|
|
|
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>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
response = None
|
|
data = None
|
|
if market['swap']:
|
|
request: dict = {
|
|
'contractName': market['id'],
|
|
}
|
|
if market['linear']:
|
|
response = self.fapiV2PrivatePostAllOpenOrders(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = 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)
|
|
|
|
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>`
|
|
"""
|
|
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 = self.fapiV2PrivateGetMyTrades(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = self.dapiV2PrivateGetMyTrades(self.extend(request, params))
|
|
data = self.safe_list(response, 'data', [])
|
|
elif market['spot']:
|
|
request['symbol'] = market['id']
|
|
response = 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)
|
|
|
|
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')
|
|
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 = 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)
|
|
|
|
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')
|
|
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 = 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,
|
|
}
|
|
|
|
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)
|
|
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 = 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
|
|
|
|
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>`
|
|
"""
|
|
self.load_markets()
|
|
response = 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',
|
|
}
|
|
|
|
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>`
|
|
"""
|
|
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 = 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)
|
|
|
|
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>`
|
|
"""
|
|
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 = self.fapiV2PrivatePostFuturesTransfer(self.extend(request, params))
|
|
#
|
|
# {
|
|
# 'code': '0',
|
|
# 'msg': 'Success',
|
|
# 'data': null
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_transfer(data, currency)
|
|
|
|
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')
|
|
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 = self.fapiV2PrivatePostLevelEdit(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = 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,
|
|
}
|
|
|
|
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>`
|
|
"""
|
|
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 = self.fapiV2PrivatePostPositionMargin(self.extend(request, params))
|
|
elif market['inverse']:
|
|
response = 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)
|