2869 lines
126 KiB
Python
2869 lines
126 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.toobit import ImplicitAPI
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Currencies, Currency, DepositAddress, Int, LedgerEntry, Leverage, Market, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFees, Transaction, MarketInterface, TransferEntry
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import OperationRejected
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import OrderNotFound
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import OperationFailed
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class toobit(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(toobit, self).describe(), {
|
|
'id': 'toobit',
|
|
'name': 'Toobit',
|
|
'countries': ['KY'], # Cayman Islands
|
|
'version': 'v1',
|
|
'rateLimit': 20, # 50 requests per second
|
|
'certified': False,
|
|
'pro': True,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': True,
|
|
'future': False,
|
|
'option': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': True,
|
|
'createOrder': True,
|
|
'fetchBalance': True,
|
|
'fetchBidsAsks': True,
|
|
'fetchCurrencies': True,
|
|
'fetchDepositAddress': True,
|
|
'fetchDeposits': True,
|
|
'fetchFundingRateHistory': True,
|
|
'fetchFundingRates': True,
|
|
'fetchIndexOHLCV': True,
|
|
'fetchLastPrices': True,
|
|
'fetchLedger': True,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': True,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenOrders': True,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': True,
|
|
'fetchStatus': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': True,
|
|
'fetchTrades': True,
|
|
'fetchWithdrawals': True,
|
|
'setMarginMode': True,
|
|
'transfer': True,
|
|
'withdraw': True,
|
|
},
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/0c7a97d5-182c-492e-b921-23540c868e0e',
|
|
'api': {
|
|
'common': 'https://api.toobit.com',
|
|
'private': 'https://api.toobit.com',
|
|
},
|
|
'www': 'https://www.toobit.com/',
|
|
'doc': [
|
|
'https://toobit-docs.github.io/apidocs/spot/v1/en/',
|
|
'https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/',
|
|
],
|
|
'referral': {
|
|
'url': 'https://www.toobit.com/en-US/r?i=IFFPy0',
|
|
'discount': 0.1,
|
|
},
|
|
'fees': 'https://www.toobit.com/fee',
|
|
},
|
|
'api': {
|
|
'common': {
|
|
'get': {
|
|
'api/v1/time': 1,
|
|
'api/v1/ping': 1,
|
|
'api/v1/exchangeInfo': 1,
|
|
'quote/v1/depth': 1, # todo: by limit 1-10
|
|
'quote/v1/depth/merged': 1,
|
|
'quote/v1/trades': 1,
|
|
'quote/v1/klines': 1,
|
|
'quote/v1/index/klines': 1,
|
|
'quote/v1/markPrice/klines': 1,
|
|
'quote/v1/markPrice': 1,
|
|
'quote/v1/index': 1,
|
|
'quote/v1/ticker/24hr': 40, # todo: 1-40 depenidng noSymbol
|
|
'quote/v1/contract/ticker/24hr': 40, # todo: 1-40 depenidng noSymbol
|
|
'quote/v1/ticker/price': 1,
|
|
'quote/v1/ticker/bookTicker': 1,
|
|
'api/v1/futures/fundingRate': 1,
|
|
'api/v1/futures/historyFundingRate': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'api/v1/account': 5,
|
|
'api/v1/account/checkApiKey': 1,
|
|
'api/v1/spot/order': 1 * 1.67,
|
|
'api/v1/spot/openOrders': 1 * 1.67,
|
|
'api/v1/futures/openOrders': 1 * 1.67,
|
|
'api/v1/spot/tradeOrders': 5 * 1.67,
|
|
'api/v1/futures/historyOrders': 5 * 1.67,
|
|
'api/v1/account/trades': 5 * 1.67,
|
|
'api/v1/account/balanceFlow': 5,
|
|
'api/v1/account/depositOrders': 5,
|
|
'api/v1/account/withdrawOrders': 5,
|
|
'api/v1/account/deposit/address': 1,
|
|
# contracts
|
|
'api/v1/subAccount': 5,
|
|
'api/v1/futures/accountLeverage': 1,
|
|
'api/v1/futures/order': 1 * 1.67,
|
|
'api/v1/futures/positions': 5 * 1.67,
|
|
'api/v1/futures/balance': 5,
|
|
'api/v1/futures/userTrades': 5 * 1.67,
|
|
'api/v1/futures/balanceFlow': 5,
|
|
'api/v1/futures/commissionRate': 5,
|
|
'api/v1/futures/todayPnl': 5,
|
|
},
|
|
'post': {
|
|
'api/v1/spot/orderTest': 1 * 1.67,
|
|
'api/v1/spot/order': 1 * 1.67,
|
|
'api/v1/futures/order': 1 * 1.67,
|
|
'api/v1/spot/batchOrders': 2 * 1.67,
|
|
'api/v1/subAccount/transfer': 1,
|
|
'api/v1/account/withdraw': 1,
|
|
# contracts
|
|
'api/v1/futures/marginType': 1,
|
|
'api/v1/futures/leverage': 1,
|
|
'api/v1/futures/batchOrders': 2 * 1.67,
|
|
'api/v1/futures/position/trading-stop': 3 * 1.67,
|
|
'api/v1/futures/positionMargin': 1,
|
|
'api/v1/userDataStream': 1,
|
|
'api/v1/listenKey': 1,
|
|
},
|
|
'delete': {
|
|
'api/v1/spot/order': 1 * 1.67,
|
|
'api/v1/futures/order': 1 * 1.67,
|
|
'api/v1/spot/openOrders': 5 * 1.67,
|
|
'api/v1/futures/batchOrders': 5 * 1.67,
|
|
'api/v1/spot/cancelOrderByIds': 5 * 1.67,
|
|
'api/v1/futures/cancelOrderByIds': 5 * 1.67,
|
|
'api/v1/listenKey': 1,
|
|
},
|
|
'put': {
|
|
'api/v1/listenKey': 1,
|
|
},
|
|
},
|
|
},
|
|
'timeframes': {
|
|
'1m': '1m',
|
|
'3m': '3m',
|
|
'5m': '5m',
|
|
'15m': '15m',
|
|
'30m': '30m',
|
|
'1h': '1h',
|
|
'2h': '2h',
|
|
'4h': '4h',
|
|
'6h': '6h',
|
|
'8h': '8h',
|
|
'12h': '12h',
|
|
'1d': '1d',
|
|
'1w': '1w',
|
|
'1M': '1M',
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'exceptions': {
|
|
'exact': {
|
|
'-1000': OperationFailed, # An unknown error occurred while processing the request.
|
|
'-1001': OperationFailed, # Internal error; unable to process your request. Please try again.
|
|
'-1002': PermissionDenied, # You are not authorized to execute self request.
|
|
'-1003': RateLimitExceeded, # TOO_MANY_REQUESTS
|
|
'-1004': BadRequest, # {"code":-1004,"msg":"Missing required parameter \u0027xyz\u0027"} | {"code":-1004,"msg":"Bad request"}
|
|
'-1006': OperationFailed, # An unexpected response was received from the message bus. Execution status unknown
|
|
'-1007': OperationFailed, # Timeout waiting for response from backend server. Send status unknown; execution status unknown.
|
|
'-1014': OperationFailed, # Unsupported order combination.
|
|
'-1015': RateLimitExceeded, # Too many new orders
|
|
'-1016': OperationRejected, # This service is no longer available.
|
|
'-1020': OperationRejected, # This operation is not supported.
|
|
'-1021': OperationRejected, # Timestamp for self request is outside of the recvWindow.
|
|
'-1022': OperationRejected, # Signature for self request is not valid.
|
|
'-1100': BadRequest, # Illegal characters found in a parameter.
|
|
'-1101': BadRequest, # Too many parameters sent for self endpoint.
|
|
'-1102': BadRequest, # A mandatory parameter was not sent, was empty/null, or malformed
|
|
'-1103': BadRequest, # An unknown parameter was sent
|
|
'-1104': BadRequest, # Not all sent parameters were read
|
|
'-1105': BadRequest, # A parameter was empty
|
|
'-1106': BadRequest, # A parameter was sent when not required
|
|
'-1111': BadRequest, # Precision is over the maximum defined for self asset.
|
|
'-1112': OperationRejected, # 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
|
|
'-1118': InvalidOrder, # New client order ID was empty.
|
|
'-1119': InvalidOrder, # Original client order ID was empty
|
|
'-1120': BadRequest, # Invalid interval
|
|
'-1121': BadRequest, # Invalid symbol
|
|
'-1125': OperationRejected, # This listenKey does not exist.
|
|
'-1127': OperationRejected, # Lookup interval is too big
|
|
'-1128': BadRequest, # Combination of optional parameters invalid
|
|
'-1130': BadRequest, # Invalid data sent for a parameter
|
|
'-1132': OperationRejected, # Order price too high
|
|
'-1133': OperationRejected, # Order price lower than the minimum,please check general broker info
|
|
'-1134': OperationRejected, # Order price decimal too long,please check general broker info
|
|
'-1135': OperationRejected, # Order quantity too large
|
|
'-1136': OperationRejected, # Order quantity lower than the minimum
|
|
'-1137': OperationRejected, # Order quantity decimal too long
|
|
'-1138': OperationRejected, # Order price exceeds permissible range
|
|
'-1139': OperationRejected, # Order has been filled
|
|
'-1140': OperationRejected, # Transaction amount lower than the minimum
|
|
'-1141': InvalidOrder, # Duplicate clientOrderId
|
|
'-1142': InvalidOrder, # Order has been canceled
|
|
'-1143': InvalidOrder, # Cannot be found on order book
|
|
'-1144': OperationRejected, # Order has been locked
|
|
'-1145': OperationRejected, # This order type does not support cancellation
|
|
'-1146': OperationFailed, # Order creation timeout
|
|
'-1147': OperationFailed, # Order cancellation timeout
|
|
'-1193': OperationRejected, # Create order count limit
|
|
'-1194': OperationRejected, # Create market order forbidden
|
|
'-1195': OperationRejected, # Create limit order price too small
|
|
'-1196': OperationRejected, # Create limit order price too big
|
|
'-1197': OperationRejected, # Create limit order buy price too big
|
|
'-1198': OperationRejected, # Create limit order sell price too small
|
|
'-1199': OperationRejected, # Create order buy quantity too small
|
|
'-1200': OperationRejected, # Create order buy quantity too big
|
|
'-1201': OperationRejected, # Create limit order sell price too big
|
|
'-1202': OperationRejected, # Create order sell quantity too small
|
|
'-1203': OperationRejected, # Create order sell quantity too big
|
|
'-1206': OperationRejected, # Orders over the maximum transaction amount
|
|
'-2010': OperationFailed, # NEW_ORDER_REJECTED
|
|
'-2011': OperationFailed, # CANCEL_REJECTED
|
|
'-2013': InvalidOrder, # Order does not exist.
|
|
'-2014': PermissionDenied, # API-key format invalid.
|
|
'-2015': PermissionDenied, # Invalid API-key, IP, or permissions for action.
|
|
'-2016': BadRequest, # No trading window could be found for the symbol. Try ticker/24hrs instead.
|
|
# errors above 3xxx are from swap API
|
|
'-3050': ExchangeError, # CREATE_API_KEY_EXCEED_LIMIT
|
|
'-3101': OperationRejected, # open margin account error
|
|
'-3102': OperationRejected, # get margin safety error
|
|
'-3103': BadRequest, # risk config is not exit
|
|
'-3105': OperationRejected, # token can not borrow
|
|
'-3107': OperationRejected, # token can not withdraw
|
|
'-3108': OperationRejected, # get token avail withdraw error
|
|
'-3109': OperationRejected, # margin withdraw failed
|
|
'-3110': InsufficientFunds, # margin avail withdraw not enough failed
|
|
'-3116': OperationRejected, # repay fail
|
|
'-3117': OperationRejected, # get margin all position fail
|
|
'-3120': OperationRejected, # get repay order fail
|
|
'-3124': OperationRejected, # Position and order data error
|
|
'-3125': OperationRejected, # Position size cannot meet target leverage
|
|
'-3126': OperationRejected, # Adjust leverage fail
|
|
'-3127': OperationFailed, # Adjust leverage timeout
|
|
'-3128': OperationRejected, # The margin mode cannot be changed while you have an open order/position
|
|
'-3129': BadRequest, # cone futures change position type error
|
|
'-3130': OperationRejected, # order margin insufficient
|
|
'-3131': NotSupported, # Leverage reduction is not supported in Isolated Margin Mode with open positions.
|
|
},
|
|
'broad': {
|
|
'Unknown order sent': OrderNotFound,
|
|
'Duplicate order sent': InvalidOrder,
|
|
'Market is closed': OperationRejected,
|
|
'Account has insufficient balance for requested action': InsufficientFunds,
|
|
'Market orders are not supported for self symbol': OperationRejected,
|
|
'Iceberg orders are not supported for self symbol': OperationRejected,
|
|
'Stop loss orders are not supported for self symbol': OperationRejected,
|
|
'Stop loss limit orders are not supported for self symbol': OperationRejected,
|
|
'Take profit orders are not supported for self symbol': OperationRejected,
|
|
'Take profit limit orders are not supported for self symbol': OperationRejected,
|
|
'QTY is zero or less': BadRequest,
|
|
'IcebergQty exceeds QTY': OperationRejected,
|
|
'This action disabled is on self account': PermissionDenied,
|
|
'Unsupported order combination': BadRequest,
|
|
'Order would trigger immediately': OperationRejected,
|
|
'Cancel order is invalid. Check origClOrdId and orderId': OperationRejected,
|
|
'Order would immediately match and take': OperationRejected,
|
|
},
|
|
},
|
|
'commonCurrencies': {},
|
|
'options': {
|
|
'defaultType': 'spot',
|
|
'accountsByType': {
|
|
'spot': 'MAIN',
|
|
'swap': 'FUTURES',
|
|
},
|
|
'networks': {
|
|
'BTC': 'BTC',
|
|
'ERC20': 'ETH',
|
|
'ETH': 'ETH',
|
|
'BEP20': 'BSC',
|
|
'TRC20': 'TRX',
|
|
'SOL': 'SOL',
|
|
'MATIC': 'MATIC',
|
|
'ARBONE': 'ARBITRUM',
|
|
'BASE': 'BASE',
|
|
'TON': 'TON',
|
|
'AVAXC': 'AVAXC',
|
|
'DOGE': 'DOGE',
|
|
'XRP': 'XRP',
|
|
'DOT': 'DOT',
|
|
'ADA': 'ADA',
|
|
'LTC': 'LTC',
|
|
'APT': 'APT',
|
|
'ATOM': 'ATOM',
|
|
'ALGO': 'ALGO',
|
|
'NEAR': 'NEAR',
|
|
'XLM': 'XLM',
|
|
'SUI': 'SUI',
|
|
'ETC': 'ETC',
|
|
'EOS': 'EOS',
|
|
'WAVES': 'WAVES',
|
|
'ICP': 'ICP',
|
|
'ONE': 'ONE',
|
|
# 'CHZ2': 'CHZ2',
|
|
},
|
|
'networksById': {
|
|
'ETH': 'ERC20',
|
|
'ERC20': 'ERC20',
|
|
},
|
|
},
|
|
'features': {
|
|
'spot': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': False,
|
|
'stopLossPrice': False,
|
|
'takeProfitPrice': False,
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'marketBuyByCost': False,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchOHLCV': {
|
|
'limit': 1000,
|
|
},
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 500,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchClosedOrders': None,
|
|
},
|
|
'forDerivatives': {
|
|
'createOrders': None,
|
|
},
|
|
'swap': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
})
|
|
|
|
def fetch_status(self, params={}):
|
|
"""
|
|
the latest known information on the availability of the exchange API
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#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.commonGetApiV1Ping(params)
|
|
return {
|
|
'status': 'ok',
|
|
'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://toobit-docs.github.io/apidocs/spot/v1/en/#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.commonGetApiV1Time(params)
|
|
#
|
|
# {
|
|
# "serverTime": 1699827319559
|
|
# }
|
|
#
|
|
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.commonGetApiV1ExchangeInfo(params)
|
|
self.options['exchangeInfo'] = response # we store it in options for later use in fetchMarkets
|
|
#
|
|
# {
|
|
# "timezone": "UTC",
|
|
# "serverTime": "1755583099926",
|
|
# "brokerFilters": [],
|
|
# "symbols": [
|
|
# {
|
|
# "filters": [
|
|
# {
|
|
# "minPrice": "0.01",
|
|
# "maxPrice": "10000000.00000000",
|
|
# "tickSize": "0.01",
|
|
# "filterType": "PRICE_FILTER"
|
|
# },
|
|
# {
|
|
# "minQty": "0.0001",
|
|
# "maxQty": "4000",
|
|
# "stepSize": "0.0001",
|
|
# "filterType": "LOT_SIZE"
|
|
# },
|
|
# {
|
|
# "minNotional": "5",
|
|
# "filterType": "MIN_NOTIONAL"
|
|
# },
|
|
# {
|
|
# "minAmount": "5",
|
|
# "maxAmount": "6600000",
|
|
# "minBuyPrice": "0.01",
|
|
# "filterType": "TRADE_AMOUNT"
|
|
# },
|
|
# {
|
|
# "maxSellPrice": "99999999",
|
|
# "buyPriceUpRate": "0.1",
|
|
# "sellPriceDownRate": "0.1",
|
|
# "filterType": "LIMIT_TRADING"
|
|
# },
|
|
# {
|
|
# "buyPriceUpRate": "0.1",
|
|
# "sellPriceDownRate": "0.1",
|
|
# "filterType": "MARKET_TRADING"
|
|
# },
|
|
# {
|
|
# "noAllowMarketStartTime": "0",
|
|
# "noAllowMarketEndTime": "0",
|
|
# "limitOrderStartTime": "0",
|
|
# "limitOrderEndTime": "0",
|
|
# "limitMinPrice": "0",
|
|
# "limitMaxPrice": "0",
|
|
# "filterType": "OPEN_QUOTE"
|
|
# }
|
|
# ],
|
|
# "exchangeId": "301",
|
|
# "symbol": "ETHUSDT",
|
|
# "symbolName": "ETHUSDT",
|
|
# "status": "TRADING",
|
|
# "baseAsset": "ETH",
|
|
# "baseAssetName": "ETH",
|
|
# "baseAssetPrecision": "0.0001",
|
|
# "quoteAsset": "USDT",
|
|
# "quoteAssetName": "USDT",
|
|
# "quotePrecision": "0.01",
|
|
# "icebergAllowed": False,
|
|
# "isAggregate": False,
|
|
# "allowMargin": True,
|
|
# }
|
|
# ],
|
|
# "options": [],
|
|
# "contracts": [
|
|
# {
|
|
# "filters": [...],
|
|
# "exchangeId": "301",
|
|
# "symbol": "BTC-SWAP-USDT",
|
|
# "symbolName": "BTC-SWAP-USDTUSDT",
|
|
# "status": "TRADING",
|
|
# "baseAsset": "BTC-SWAP-USDT",
|
|
# "baseAssetPrecision": "0.001",
|
|
# "quoteAsset": "USDT",
|
|
# "quoteAssetPrecision": "0.1",
|
|
# "icebergAllowed": False,
|
|
# "inverse": False,
|
|
# "index": "BTC",
|
|
# "indexToken": "BTCUSDT",
|
|
# "marginToken": "USDT",
|
|
# "marginPrecision": "0.0001",
|
|
# "contractMultiplier": "0.001",
|
|
# "underlying": "BTC",
|
|
# "riskLimits": [
|
|
# {
|
|
# "riskLimitId": "200020911",
|
|
# "quantity": "42000.0",
|
|
# "initialMargin": "0.02",
|
|
# "maintMargin": "0.01",
|
|
# "isWhite": False
|
|
# },
|
|
# {
|
|
# "riskLimitId": "200020912",
|
|
# "quantity": "84000.0",
|
|
# "initialMargin": "0.04",
|
|
# "maintMargin": "0.02",
|
|
# "isWhite": False
|
|
# },
|
|
# ...
|
|
# ]
|
|
# },
|
|
# ],
|
|
# "coins": [
|
|
# {
|
|
# "orgId": "9001",
|
|
# "coinId": "TCOM",
|
|
# "coinName": "TCOM",
|
|
# "coinFullName": "TCOM",
|
|
# "allowWithdraw": True,
|
|
# "allowDeposit": True,
|
|
# "chainTypes": [
|
|
# {
|
|
# "chainType": "BSC",
|
|
# "withdrawFee": "49.55478",
|
|
# "minWithdrawQuantity": "77",
|
|
# "maxWithdrawQuantity": "0",
|
|
# "minDepositQuantity": "48",
|
|
# "allowDeposit": True,
|
|
# "allowWithdraw": False
|
|
# }
|
|
# ],
|
|
# "isVirtual": False
|
|
# },
|
|
# ...
|
|
#
|
|
coins = self.safe_list(response, 'coins', [])
|
|
result = {}
|
|
for i in range(0, len(coins)):
|
|
coin = coins[i]
|
|
parsed = self.parse_currency(coin)
|
|
code = parsed['code']
|
|
result[code] = parsed
|
|
return result
|
|
|
|
def parse_currency(self, rawCurrency: dict) -> Currency:
|
|
id = self.safe_string(rawCurrency, 'coinId')
|
|
code = self.safe_currency_code(id)
|
|
networks: dict = {}
|
|
rawNetworks = self.safe_list(rawCurrency, 'chainTypes')
|
|
for j in range(0, len(rawNetworks)):
|
|
rawNetwork = rawNetworks[j]
|
|
networkId = self.safe_string(rawNetwork, 'chainType')
|
|
networkCode = self.network_id_to_code(networkId)
|
|
networks[networkCode] = {
|
|
'id': networkId,
|
|
'network': networkCode,
|
|
'margin': None,
|
|
'deposit': self.safe_bool(rawNetwork, 'allowDeposit'),
|
|
'withdraw': self.safe_bool(rawNetwork, 'allowWithdraw'),
|
|
'active': None,
|
|
'fee': self.safe_number(rawNetwork, 'withdrawFee'),
|
|
'precision': None,
|
|
'limits': {
|
|
'deposit': {
|
|
'min': self.safe_number(rawNetwork, 'minDepositQuantity'),
|
|
'max': None,
|
|
},
|
|
'withdraw': {
|
|
'min': self.safe_number(rawNetwork, 'minWithdrawQuantity'),
|
|
'max': self.safe_number(rawNetwork, 'maxWithdrawQuantity'),
|
|
},
|
|
},
|
|
'info': rawNetwork,
|
|
}
|
|
return self.safe_currency_structure({
|
|
'id': id,
|
|
'code': code,
|
|
'name': self.safe_string(rawCurrency, 'coinFullName'),
|
|
'type': None,
|
|
'active': None,
|
|
'deposit': self.safe_bool(rawCurrency, 'allowDeposit'),
|
|
'withdraw': self.safe_bool(rawCurrency, 'allowWithdraw'),
|
|
'fee': None,
|
|
'precision': None,
|
|
'limits': {
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'withdraw': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'networks': networks,
|
|
'info': rawCurrency,
|
|
})
|
|
|
|
def fetch_markets(self, params={}) -> List[MarketInterface]:
|
|
"""
|
|
retrieves data on all markets for toobit
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#exchange-information
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#exchange-information
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = self.safe_dict(self.options, 'exchangeInfo')
|
|
if response is not None:
|
|
self.options['exchangeInfo'] = None # reset it to avoid using old cached data
|
|
else:
|
|
response = self.commonGetApiV1ExchangeInfo(params)
|
|
#
|
|
# {
|
|
# "timezone": "UTC",
|
|
# "serverTime": "1755583099926",
|
|
# "brokerFilters": [],
|
|
# "symbols": [
|
|
# {
|
|
# "filters": [
|
|
# {
|
|
# "minPrice": "0.01",
|
|
# "maxPrice": "10000000.00000000",
|
|
# "tickSize": "0.01",
|
|
# "filterType": "PRICE_FILTER"
|
|
# },
|
|
# {
|
|
# "minQty": "0.0001",
|
|
# "maxQty": "4000",
|
|
# "stepSize": "0.0001",
|
|
# "filterType": "LOT_SIZE"
|
|
# },
|
|
# {
|
|
# "minNotional": "5",
|
|
# "filterType": "MIN_NOTIONAL"
|
|
# },
|
|
# {
|
|
# "minAmount": "5",
|
|
# "maxAmount": "6600000",
|
|
# "minBuyPrice": "0.01",
|
|
# "filterType": "TRADE_AMOUNT"
|
|
# },
|
|
# {
|
|
# "maxSellPrice": "99999999",
|
|
# "buyPriceUpRate": "0.1",
|
|
# "sellPriceDownRate": "0.1",
|
|
# "filterType": "LIMIT_TRADING"
|
|
# },
|
|
# {
|
|
# "buyPriceUpRate": "0.1",
|
|
# "sellPriceDownRate": "0.1",
|
|
# "filterType": "MARKET_TRADING"
|
|
# },
|
|
# {
|
|
# "noAllowMarketStartTime": "0",
|
|
# "noAllowMarketEndTime": "0",
|
|
# "limitOrderStartTime": "0",
|
|
# "limitOrderEndTime": "0",
|
|
# "limitMinPrice": "0",
|
|
# "limitMaxPrice": "0",
|
|
# "filterType": "OPEN_QUOTE"
|
|
# }
|
|
# ],
|
|
# "exchangeId": "301",
|
|
# "symbol": "ETHUSDT",
|
|
# "symbolName": "ETHUSDT",
|
|
# "status": "TRADING",
|
|
# "baseAsset": "ETH",
|
|
# "baseAssetName": "ETH",
|
|
# "baseAssetPrecision": "0.0001",
|
|
# "quoteAsset": "USDT",
|
|
# "quoteAssetName": "USDT",
|
|
# "quotePrecision": "0.01",
|
|
# "icebergAllowed": False,
|
|
# "isAggregate": False,
|
|
# "allowMargin": True,
|
|
# }
|
|
# ],
|
|
# "options": [],
|
|
# "contracts": [
|
|
# {
|
|
# "filters": [...],
|
|
# "exchangeId": "301",
|
|
# "symbol": "BTC-SWAP-USDT",
|
|
# "symbolName": "BTC-SWAP-USDTUSDT",
|
|
# "status": "TRADING",
|
|
# "baseAsset": "BTC-SWAP-USDT",
|
|
# "baseAssetPrecision": "0.001",
|
|
# "quoteAsset": "USDT",
|
|
# "quoteAssetPrecision": "0.1",
|
|
# "icebergAllowed": False,
|
|
# "inverse": False,
|
|
# "index": "BTC",
|
|
# "indexToken": "BTCUSDT",
|
|
# "marginToken": "USDT",
|
|
# "marginPrecision": "0.0001",
|
|
# "contractMultiplier": "0.001",
|
|
# "underlying": "BTC",
|
|
# "riskLimits": [
|
|
# {
|
|
# "riskLimitId": "200020911",
|
|
# "quantity": "42000.0",
|
|
# "initialMargin": "0.02",
|
|
# "maintMargin": "0.01",
|
|
# "isWhite": False
|
|
# },
|
|
# {
|
|
# "riskLimitId": "200020912",
|
|
# "quantity": "84000.0",
|
|
# "initialMargin": "0.04",
|
|
# "maintMargin": "0.02",
|
|
# "isWhite": False
|
|
# },
|
|
# ...
|
|
# ]
|
|
# },
|
|
# ],
|
|
# "coins": [
|
|
# {
|
|
# "orgId": "9001",
|
|
# "coinId": "TCOM",
|
|
# "coinName": "TCOM",
|
|
# "coinFullName": "TCOM",
|
|
# "allowWithdraw": True,
|
|
# "allowDeposit": True,
|
|
# "chainTypes": [
|
|
# {
|
|
# "chainType": "BSC",
|
|
# "withdrawFee": "49.55478",
|
|
# "minWithdrawQuantity": "77",
|
|
# "maxWithdrawQuantity": "0",
|
|
# "minDepositQuantity": "48",
|
|
# "allowDeposit": True,
|
|
# "allowWithdraw": False
|
|
# }
|
|
# ],
|
|
# "isVirtual": False
|
|
# },
|
|
# ...
|
|
#
|
|
symbols = self.safe_list(response, 'symbols', [])
|
|
contracts = self.safe_list(response, 'contracts', [])
|
|
all = self.array_concat(symbols, contracts)
|
|
result = []
|
|
for i in range(0, len(all)):
|
|
market = all[i]
|
|
parsed = self.parse_market(market)
|
|
result.append(parsed)
|
|
return result
|
|
|
|
def parse_market(self, market: dict) -> Market:
|
|
id = self.safe_string(market, 'symbol')
|
|
baseId = self.safe_string(market, 'baseAsset')
|
|
quoteId = self.safe_string(market, 'quoteAsset')
|
|
baseParts = baseId.split('-')
|
|
baseIdClean = baseParts[0]
|
|
base = self.safe_currency_code(baseIdClean)
|
|
quote = self.safe_currency_code(quoteId)
|
|
settleId = self.safe_string(market, 'marginToken')
|
|
settle = self.safe_currency_code(settleId)
|
|
status = self.safe_string(market, 'status')
|
|
active = (status == 'TRADING')
|
|
filters = self.safe_list(market, 'filters', [])
|
|
filtersByType = self.index_by(filters, 'filterType')
|
|
priceFilter = self.safe_dict(filtersByType, 'PRICE_FILTER', {})
|
|
lotSizeFilter = self.safe_dict(filtersByType, 'LOT_SIZE', {})
|
|
minNotionalFilter = self.safe_dict(filtersByType, 'MIN_NOTIONAL', {})
|
|
symbol = base + '/' + quote
|
|
isContract = ('contractMultiplier' in market)
|
|
inverse = self.safe_bool_2(market, 'isInverse', 'inverse')
|
|
if isContract:
|
|
symbol += ':' + settle
|
|
return self.safe_market_structure({
|
|
'id': id,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': settle,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': settleId,
|
|
'type': 'swap' if isContract else 'spot',
|
|
'spot': not isContract,
|
|
'margin': False,
|
|
'swap': isContract,
|
|
'future': False,
|
|
'option': False,
|
|
'active': active,
|
|
'contract': isContract,
|
|
'linear': not inverse if isContract else None,
|
|
'inverse': inverse if isContract else None,
|
|
'contractSize': self.safe_number(market, 'contractMultiplier'),
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': self.safe_number(lotSizeFilter, 'stepSize'),
|
|
'price': self.safe_number(priceFilter, 'tickSize'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': self.safe_number(lotSizeFilter, 'minQty'),
|
|
'max': self.safe_number(lotSizeFilter, 'maxQty'),
|
|
},
|
|
'price': {
|
|
'min': self.safe_number(priceFilter, 'minPrice'),
|
|
'max': self.safe_number(priceFilter, 'maxPrice'),
|
|
},
|
|
'cost': {
|
|
'min': self.safe_number(minNotionalFilter, 'minNotional'),
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None,
|
|
'info': market,
|
|
})
|
|
|
|
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#order-book
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#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)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = self.commonGetQuoteV1Depth(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "t": "1755593995237",
|
|
# "b": [
|
|
# [
|
|
# "115186.47",
|
|
# "4.184864"
|
|
# ],
|
|
# [
|
|
# "115186.46",
|
|
# "0.002756"
|
|
# ],
|
|
# ...
|
|
# ],
|
|
# "a": [
|
|
# [
|
|
# "115186.48",
|
|
# "6.137369"
|
|
# ],
|
|
# [
|
|
# "115186.49",
|
|
# "0.002914"
|
|
# ],
|
|
# ...
|
|
# ]
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(response, 't')
|
|
return self.parse_order_book(response, market['symbol'], timestamp, 'b', 'a')
|
|
|
|
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get a list of the most recent trades for a particular symbol
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#recent-trades-list
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#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 number 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)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = self.commonGetQuoteV1Trades(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "t": "1755594277287",
|
|
# "p": "115276.99",
|
|
# "q": "0.001508",
|
|
# "ibm": True
|
|
# },
|
|
# ]
|
|
#
|
|
return self.parse_trades(response, market, since, limit)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades
|
|
#
|
|
# {
|
|
# "t": "1755594277287",
|
|
# "p": "115276.99",
|
|
# "q": "0.001508",
|
|
# "ibm": True
|
|
# },
|
|
# # watchTrades have also an additional fields:
|
|
# "v": "4864732022868004630", # trade id
|
|
# "m": True, # is the buyer taker
|
|
#
|
|
# fetchMyTrades
|
|
#
|
|
# {
|
|
# "id": "2024934575206059008",
|
|
# "symbol": "ETHUSDT",
|
|
# "orderId": "2024934575097029888",
|
|
# "ticketId": "4864450547563401875",
|
|
# "price": "4641.21",
|
|
# "qty": "0.001",
|
|
# "time": "1756127012094",
|
|
# "isMaker": False,
|
|
# "commission": "0.00464121",
|
|
# "commissionAsset": "USDT",
|
|
# "makerRebate": "0",
|
|
# "symbolName": "ETHUSDT", # only in SPOT
|
|
# "isBuyer": False, # only in SPOT
|
|
# "feeAmount": "0.00464121", # only in SPOT
|
|
# "feeCoinId": "USDT", # only in SPOT
|
|
# "fee": { # only in SPOT
|
|
# "feeCoinId": "USDT",
|
|
# "feeCoinName": "USDT",
|
|
# "fee": "0.00464121"
|
|
# },
|
|
# "type": "LIMIT", # only in CONTRACT
|
|
# "side": "BUY_OPEN", # only in CONTRACT
|
|
# "realizedPnl": "0", # only in CONTRACT
|
|
# },
|
|
#
|
|
timestamp = self.safe_integer_2(trade, 't', 'time')
|
|
priceString = self.safe_string_2(trade, 'p', 'price')
|
|
amountString = self.safe_string_2(trade, 'q', 'qty')
|
|
isBuyer = self.safe_bool(trade, 'isBuyer')
|
|
side = None
|
|
isBuyerMaker = self.safe_bool(trade, 'ibm')
|
|
if isBuyerMaker is None:
|
|
isBuyerTaker = self.safe_bool(trade, 'm')
|
|
if isBuyerTaker is not None:
|
|
isBuyerMaker = not isBuyerTaker
|
|
if isBuyerMaker is not None:
|
|
if isBuyerMaker:
|
|
side = 'sell'
|
|
else:
|
|
side = 'buy'
|
|
else:
|
|
if isBuyer:
|
|
side = 'buy'
|
|
else:
|
|
side = 'sell'
|
|
feeCurrencyId = self.safe_string(trade, 'feeCoinId')
|
|
feeAmount = self.safe_string(trade, 'feeAmount')
|
|
fee = None
|
|
if feeAmount is not None:
|
|
fee = {
|
|
'currency': self.safe_currency_code(feeCurrencyId),
|
|
'cost': feeAmount,
|
|
}
|
|
isMaker = self.safe_bool(trade, 'isMaker')
|
|
takerOrMaker = None
|
|
if isMaker is not None:
|
|
takerOrMaker = 'maker' if isMaker else 'taker'
|
|
market = self.safe_market(None, market)
|
|
symbol = market['symbol']
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'id': self.safe_string_2(trade, 'id', 'v'),
|
|
'order': self.safe_string(trade, 'orderId'),
|
|
'type': None,
|
|
'side': side,
|
|
'amount': amountString,
|
|
'price': priceString,
|
|
'cost': None,
|
|
'takerOrMaker': takerOrMaker,
|
|
'fee': fee,
|
|
}, market)
|
|
|
|
def fetch_ohlcv(self, symbol: str, timeframe='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://toobit-docs.github.io/apidocs/spot/v1/en/#kline-candlestick-data
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#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
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
}
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
params = self.omit(params, 'until')
|
|
request['endTime'] = until
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = None
|
|
endpoint = None
|
|
endpoint, params = self.handle_option_and_params(params, 'fetchOHLCV', 'price')
|
|
if endpoint == 'index':
|
|
response = self.commonGetQuoteV1IndexKlines(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 200,
|
|
# "data": [
|
|
# {
|
|
# "t": 1669155300000,//time
|
|
# "s": "ETHUSDT",// symbol
|
|
# "sn": "ETHUSDT",//symbol name
|
|
# "c": "1127.1",//Close price
|
|
# "h": "1130.81",//High price
|
|
# "l": "1126.17",//Low price
|
|
# "o": "1130.8",//Open price
|
|
# "v": "0"//Volume
|
|
# },
|
|
# {
|
|
# "t": 1669156200000,
|
|
# "s": "ETHUSDT",
|
|
# "sn": "ETHUSDT",
|
|
# "c": "1129.44",
|
|
# "h": "1129.54",
|
|
# "l": "1127.1",
|
|
# "o": "1127.1",
|
|
# "v": "0"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
elif endpoint == 'mark':
|
|
response = self.commonGetQuoteV1MarkPriceKlines(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 200,
|
|
# "data": [
|
|
# {
|
|
# "symbol": "BTCUSDT",// Symbol
|
|
# "time": 1670157900000,// time
|
|
# "low": "16991.14096",//Low price
|
|
# "open": "16991.78288",//Open price
|
|
# "high": "16996.30641",// High prce
|
|
# "close": "16996.30641",// Close price
|
|
# "volume": "0",// Volume
|
|
# "curId": 1670157900000
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
else:
|
|
response = self.commonGetQuoteV1Klines(self.extend(request, params))
|
|
#
|
|
# [
|
|
# [
|
|
# 1755540660000,
|
|
# "116399.99",
|
|
# "116399.99",
|
|
# "116360.09",
|
|
# "116360.1",
|
|
# "2.236869",
|
|
# 0,
|
|
# "260303.79722607",
|
|
# 22,
|
|
# "2.221061",
|
|
# "258464.10338267"
|
|
# ],
|
|
# ...
|
|
#
|
|
return self.parse_ohlcvs(response, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
return [
|
|
self.safe_integer_n(ohlcv, [0, 'time', 't']),
|
|
self.safe_number_n(ohlcv, [1, 'open', 'o']),
|
|
self.safe_number_n(ohlcv, [2, 'high', 'h']),
|
|
self.safe_number_n(ohlcv, [3, 'low', 'l']),
|
|
self.safe_number_n(ohlcv, [4, 'close', 'c']),
|
|
self.safe_number_n(ohlcv, [5, 'volume', 'v']),
|
|
]
|
|
|
|
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://toobit-docs.github.io/apidocs/spot/v1/en/#24hr-ticker-price-change-statistics
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#24hr-ticker-price-change-statistics
|
|
|
|
: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)
|
|
type = None
|
|
market = None
|
|
request: dict = {}
|
|
if symbols is not None:
|
|
symbol = self.safe_string(symbols, 0)
|
|
market = self.market(symbol)
|
|
length = len(symbols)
|
|
if length == 1:
|
|
request['symbol'] = market['id']
|
|
type, params = self.handle_market_type_and_params('fetchTickers', market, params)
|
|
response = None
|
|
if type == 'spot':
|
|
response = self.commonGetQuoteV1Ticker24hr(self.extend(request, params))
|
|
else:
|
|
response = self.commonGetQuoteV1ContractTicker24hr(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "t": "1755601440162",
|
|
# "s": "GRDRUSDT",
|
|
# "o": "0.38",
|
|
# "h": "0.38",
|
|
# "l": "0.38",
|
|
# "c": "0.38",
|
|
# "v": "0",
|
|
# "qv": "0",
|
|
# "pc": "0",
|
|
# "pcp": "0"
|
|
# },
|
|
# ...
|
|
#
|
|
return self.parse_tickers(response, symbols, params)
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
marketId = self.safe_string(ticker, 's')
|
|
market = self.safe_market(marketId, market)
|
|
timestamp = self.safe_integer(ticker, 't')
|
|
last = self.safe_string(ticker, 'c')
|
|
return self.safe_ticker({
|
|
'symbol': market['symbol'],
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_string(ticker, 'h'),
|
|
'low': self.safe_string(ticker, 'l'),
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': self.safe_string(ticker, 'o'),
|
|
'close': last,
|
|
'last': last,
|
|
'previousClose': None,
|
|
'change': self.safe_string(ticker, 'pc'),
|
|
'percentage': self.safe_string(ticker, 'pcp'),
|
|
'average': None,
|
|
'baseVolume': self.safe_string(ticker, 'v'),
|
|
'quoteVolume': self.safe_string(ticker, 'qv'),
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
def fetch_last_prices(self, symbols: Strings = None, params={}):
|
|
"""
|
|
fetches the last price for multiple markets
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#symbol-price-ticker
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#symbol-price-ticker
|
|
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the last prices
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of lastprices structures
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
request: dict = {}
|
|
if symbols is not None:
|
|
length = len(symbols)
|
|
if length == 1:
|
|
market = self.market(symbols[0])
|
|
request['symbol'] = market['id']
|
|
response = self.commonGetQuoteV1TickerPrice(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "s": "BNTUSDT",
|
|
# "si": "BNTUSDT",
|
|
# "p": "0.823"
|
|
# },
|
|
#
|
|
return self.parse_last_prices(response, symbols)
|
|
|
|
def parse_last_price(self, entry, market: Market = None):
|
|
marketId = self.safe_string(entry, 's')
|
|
market = self.safe_market(marketId, market)
|
|
return {
|
|
'symbol': market['symbol'],
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'price': self.safe_number_omit_zero(entry, 'price'),
|
|
'side': None,
|
|
'info': entry,
|
|
}
|
|
|
|
def fetch_bids_asks(self, symbols: Strings = None, params={}):
|
|
"""
|
|
fetches the bid and ask price and volume for multiple markets
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#symbol-order-book-ticker
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#symbol-order-book-ticker
|
|
|
|
:param str[] [symbols]: unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the 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)
|
|
request: dict = {}
|
|
if symbols is not None:
|
|
length = len(symbols)
|
|
if length == 1:
|
|
market = self.market(symbols[0])
|
|
request['symbol'] = market['id']
|
|
response = self.commonGetQuoteV1TickerBookTicker(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "s": "GRDRUSDT",
|
|
# "b": "0",
|
|
# "bq": "0",
|
|
# "a": "0",
|
|
# "aq": "0",
|
|
# "t": "1755936610506"
|
|
# }, ...
|
|
#
|
|
return self.parse_bids_asks_custom(response, symbols)
|
|
|
|
def parse_bids_asks_custom(self, tickers, symbols: Strings = None, params={}) -> Tickers:
|
|
results = []
|
|
for i in range(0, len(tickers)):
|
|
parsedTicker = self.parse_bid_ask_custom(tickers[i])
|
|
ticker = self.extend(parsedTicker, params)
|
|
results.append(ticker)
|
|
symbols = self.market_symbols(symbols)
|
|
return self.filter_by_array(results, 'symbol', symbols)
|
|
|
|
def parse_bid_ask_custom(self, ticker):
|
|
return {
|
|
'timestamp': self.safe_string(ticker, 't'),
|
|
'symbol': self.safe_string(ticker, 's'),
|
|
'bid': self.safe_number(ticker, 'b'),
|
|
'bidVolume': self.safe_number(ticker, 'bq'),
|
|
'ask': self.safe_number(ticker, 'a'),
|
|
'askVolume': self.safe_number(ticker, 'aq'),
|
|
'info': ticker,
|
|
}
|
|
|
|
def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
|
|
"""
|
|
fetch the funding rate for multiple markets
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#funding-rate
|
|
|
|
:param str[]|None symbols: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `funding rates structures <https://docs.ccxt.com/#/?id=funding-rates-structure>`, indexe by market symbols
|
|
"""
|
|
self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
request: dict = {}
|
|
if symbols is not None:
|
|
length = len(symbols)
|
|
if length == 1:
|
|
market = self.market(symbols[0])
|
|
request['symbol'] = market['id']
|
|
response = self.commonGetApiV1FuturesFundingRate(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "symbol": "BTC-SWAP-USDT",
|
|
# "rate": "0.0001071148112848",
|
|
# "nextFundingTime": "1755964800000"
|
|
# },...
|
|
#
|
|
return self.parse_funding_rates(response, symbols)
|
|
|
|
def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
|
|
marketId = self.safe_string(contract, 'symbol')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
nextFundingRate = self.safe_number(contract, 'rate')
|
|
nextFundingRateTimestamp = self.safe_integer(contract, 'nextFundingTime')
|
|
return {
|
|
'info': contract,
|
|
'symbol': symbol,
|
|
'markPrice': None,
|
|
'indexPrice': None,
|
|
'interestRate': None,
|
|
'estimatedSettlePrice': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'previousFundingRate': None,
|
|
'nextFundingRate': None,
|
|
'previousFundingTimestamp': None,
|
|
'nextFundingTimestamp': None,
|
|
'previousFundingDatetime': None,
|
|
'nextFundingDatetime': None,
|
|
'fundingRate': nextFundingRate,
|
|
'fundingTimestamp': nextFundingRateTimestamp,
|
|
'fundingDatetime': self.iso8601(nextFundingRateTimestamp),
|
|
'interval': None,
|
|
}
|
|
|
|
def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetches historical funding rate prices
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#get-funding-rate-history
|
|
|
|
:param str symbol: unified symbol of the market to fetch the funding rate history for
|
|
:param int [since]: timestamp in ms of the earliest funding rate to fetch
|
|
:param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: timestamp in ms of the latest funding rate to fetch
|
|
:param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
|
|
:returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
|
|
"""
|
|
self.load_markets()
|
|
paginate = False
|
|
paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
|
|
if paginate:
|
|
return self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params)
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = self.commonGetApiV1FuturesHistoryFundingRate(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id": "869931",
|
|
# "symbol": "BTC-SWAP-USDT",
|
|
# "settleTime": "1755936000000",
|
|
# "settleRate": "0.0001"
|
|
# }, ...
|
|
#
|
|
return self.parse_funding_rate_histories(response, market, since, limit)
|
|
|
|
def parse_funding_rate_history(self, contract, market: Market = None):
|
|
timestamp = self.safe_integer(contract, 'settleTime')
|
|
marketId = self.safe_string(contract, 'symbol')
|
|
return {
|
|
'info': contract,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'fundingRate': self.safe_number(contract, 'settleRate'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
}
|
|
|
|
def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#account-information-user_data
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#futures-account-balance-user_data
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpointinvalid
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
self.load_markets()
|
|
response = None
|
|
marketType: Str = None
|
|
marketType, params = self.handle_market_type_and_params('fetchBalance', None, params)
|
|
if self.in_array(marketType, ['swap', 'future']):
|
|
response = self.privateGetApiV1FuturesBalance()
|
|
#
|
|
# [
|
|
# {
|
|
# "asset": "USDT", # asset
|
|
# "balance": "999999999999.982", # total
|
|
# "availableBalance": "1899999999978.4995", # available balance Include unrealized pnl
|
|
# "positionMargin": "11.9825", #position Margin
|
|
# "orderMargin": "9.5", #order Margin
|
|
# "crossUnRealizedPnl": "10.01" #The unrealized profit and loss of cross position
|
|
# }
|
|
# ]
|
|
#
|
|
else:
|
|
response = self.privateGetApiV1Account()
|
|
#
|
|
# {
|
|
# "userId": "912902020",
|
|
# "balances": [
|
|
# {
|
|
# "asset": "ETH",
|
|
# "assetId": "ETH",
|
|
# "assetName": "ETH",
|
|
# "total": "0.025",
|
|
# "free": "0.025",
|
|
# "locked": "0"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_balance(response)
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
result: dict = {
|
|
'info': response,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
}
|
|
balances = self.safe_list(response, 'balances', response)
|
|
for i in range(0, len(balances)):
|
|
balance = balances[i]
|
|
code = self.safe_currency_code(self.safe_string(balance, 'asset'))
|
|
account = self.account()
|
|
account['free'] = self.safe_string_2(balance, 'free', 'availableBalance')
|
|
account['total'] = self.safe_string_2(balance, 'total', 'balance')
|
|
account['used'] = self.safe_string(balance, 'locked')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#new-order-trade
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#new-order-trade
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market', 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much of currency you want to trade in units of base currency
|
|
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request = {}
|
|
response = None
|
|
if market['spot']:
|
|
request, params = self.create_order_request(symbol, type, side, amount, price, params)
|
|
response = self.privatePostApiV1SpotOrder(self.extend(request, params))
|
|
else:
|
|
request, params = self.create_contract_order_request(symbol, type, side, amount, price, params)
|
|
response = self.privatePostApiV1FuturesOrder(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "symbol": "ETHUSDT",
|
|
# "price": "0",
|
|
# "origQty": "0.001",
|
|
# "orderId": "2024837825254460160",
|
|
# "clientOrderId": "1756115478113679",
|
|
# "executedQty": "0",
|
|
# "status": "PENDING_NEW",
|
|
# "timeInForce": "GTC",
|
|
# "type": "MARKET",
|
|
# "side": "SELL"
|
|
# "accountId": "1783404067076253952", # only in spot
|
|
# "symbolName": "ETHUSDT", # only in spot
|
|
# "transactTime": "1756115478604", # only in spot
|
|
# "time": "1668418485058", # only in contract
|
|
# "updateTime": "1668418485058", # only in contract
|
|
# "leverage": "2", # only in contract
|
|
# "avgPrice": "0", # only in contract
|
|
# "marginLocked": "9.5", # only in contract
|
|
# "priceType": "INPUT" # only in contract
|
|
# }
|
|
#
|
|
return self.parse_order(response, market)
|
|
|
|
def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
market = self.market(symbol)
|
|
id = market['id']
|
|
request: dict = {
|
|
'symbol': id,
|
|
'side': side.upper(),
|
|
}
|
|
if price is not None:
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
cost: Str = None
|
|
cost, params = self.handle_param_string(params, 'cost')
|
|
if type == 'market':
|
|
if cost is None and side == 'buy':
|
|
raise ArgumentsRequired(self.id + ' createOrder() requires params["cost"] for market buy order')
|
|
else:
|
|
request['quantity'] = self.cost_to_precision(symbol, cost)
|
|
else:
|
|
request['quantity'] = self.amount_to_precision(symbol, amount)
|
|
isPostOnly = None
|
|
isPostOnly, params = self.handle_post_only(type == 'market', False, params)
|
|
if isPostOnly:
|
|
request['type'] = 'LIMIT_MAKER'
|
|
else:
|
|
request['type'] = type.upper()
|
|
return [request, params]
|
|
|
|
def create_contract_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'quantity': self.amount_to_precision(symbol, amount),
|
|
}
|
|
reduceOnly = None
|
|
reduceOnly, params = self.handle_param_bool(params, 'reduceOnly')
|
|
if side == 'buy':
|
|
side = 'SELL_CLOSE' if reduceOnly else 'BUY_OPEN'
|
|
elif side == 'sell':
|
|
side = 'BUY_CLOSE' if reduceOnly else 'SELL_OPEN'
|
|
request['side'] = side
|
|
if price is not None:
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
if self.in_array(type, ['limit', 'LIMIT']):
|
|
request['type'] = type.upper()
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
elif type == 'market':
|
|
request['type'] = 'LIMIT' # weird, but exchange works self way
|
|
request['priceType'] = 'MARKET'
|
|
isPostOnly = None
|
|
isPostOnly, params = self.handle_post_only(type == 'market', False, params)
|
|
if isPostOnly:
|
|
request['timeInForce'] = 'LIMIT_MAKER'
|
|
values = self.handle_trigger_prices_and_params(symbol, params)
|
|
triggerPrice = values[0]
|
|
params = values[3]
|
|
if triggerPrice is not None:
|
|
request['stopPrice'] = triggerPrice
|
|
stopLoss = self.safe_dict(params, 'stopLoss')
|
|
takeProfit = self.safe_dict(params, 'takeProfit')
|
|
triggerPriceTypes = {
|
|
'mark': 'MARK_PRICE',
|
|
'last': 'CONTRACT_PRICE',
|
|
}
|
|
if stopLoss is not None:
|
|
request['stopLoss'] = self.safe_value(stopLoss, 'triggerPrice')
|
|
limitPrice = self.safe_value(stopLoss, 'price')
|
|
if limitPrice is not None:
|
|
request['slOrderType'] = 'LIMIT'
|
|
request['slLimitPrice'] = self.price_to_precision(symbol, limitPrice)
|
|
triggerPriceType = self.safe_string(stopLoss, 'triggerPriceType')
|
|
if triggerPriceType is not None:
|
|
request['slTriggerBy'] = self.safe_string(triggerPriceTypes, triggerPriceType, triggerPriceType)
|
|
params = self.omit(params, 'stopLoss')
|
|
if takeProfit is not None:
|
|
request['takeProfit'] = self.safe_value(takeProfit, 'triggerPrice')
|
|
limitPrice = self.safe_value(takeProfit, 'price')
|
|
if limitPrice is not None:
|
|
request['tpOrderType'] = 'LIMIT'
|
|
request['tpLimitPrice'] = self.price_to_precision(symbol, limitPrice)
|
|
triggerPriceType = self.safe_string(takeProfit, 'triggerPriceType')
|
|
if triggerPriceType is not None:
|
|
request['tpTriggerBy'] = self.safe_string(triggerPriceTypes, triggerPriceType, triggerPriceType)
|
|
params = self.omit(params, 'takeProfit')
|
|
if not ('newClientOrderId' in params):
|
|
request['newClientOrderId'] = self.uuid()
|
|
return [request, params]
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# createOrder, cancelOrder
|
|
#
|
|
# {
|
|
# "symbol": "ETHUSDT",
|
|
# "price": "0",
|
|
# "origQty": "0.001",
|
|
# "orderId": "2024837825254460160",
|
|
# "clientOrderId": "1756115478113679",
|
|
# "executedQty": "0",
|
|
# "status": "PENDING_NEW",
|
|
# "timeInForce": "GTC",
|
|
# "type": "MARKET",
|
|
# "side": "SELL"
|
|
# "accountId": "1783404067076253952", # only in spot
|
|
# "symbolName": "ETHUSDT", # only in spot
|
|
# "transactTime": "1756115478604", # only in spot
|
|
# "time": "1668418485058", # only in contract
|
|
# "updateTime": "1668418485058", # only in contract
|
|
# "leverage": "2", # only in contract
|
|
# "avgPrice": "0", # only in contract
|
|
# "marginLocked": "9.5", # only in contract
|
|
# "priceType": "INPUT" # only in contract
|
|
# }
|
|
#
|
|
#
|
|
# fetchOrder, fetchOrders, fetchOpenOrders
|
|
#
|
|
# {
|
|
# "time": "1756140208069",
|
|
# "updateTime": "1756140208078",
|
|
# "orderId": "2025045271033977089",
|
|
# "clientOrderId": "17561402075722006",
|
|
# "symbol": "ETHUSDT",
|
|
# "price": "3000",
|
|
# "origQty": "0.002",
|
|
# "executedQty": "0",
|
|
# "avgPrice": "0",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "timeInForce": "GTC",
|
|
# "status": "NEW",
|
|
# "accountId": "1783404067076253952", # only in SPOT
|
|
# "exchangeId": "301", # only in SPOT
|
|
# "symbolName": "ETHUSDT", # only in SPOT
|
|
# "cummulativeQuoteQty": "0", # only in SPOT
|
|
# "cumulativeQuoteQty": "0", # only in SPOT
|
|
# "stopPrice": "0.0", # only in SPOT
|
|
# "icebergQty": "0.0", # only in SPOT
|
|
# "isWorking": True # only in SPOT
|
|
# "leverage": "2", # only in CONTRACT
|
|
# "marginLocked": "9.5", # only in CONTRACT
|
|
# "priceType": "INPUT" # only in CONTRACT
|
|
# "triggerType": "0", # only in CONTRACT fetchClosedOrders
|
|
# "fallType": "0", # only in CONTRACT fetchClosedOrders
|
|
# "activeStatus": "0" # only in CONTRACT fetchClosedOrders
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer_2(order, 'transactTime', 'time')
|
|
marketId = self.safe_string(order, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
rawType = self.safe_string(order, 'type')
|
|
rawSideLower = self.safe_string_lower(order, 'side')
|
|
triggerPrice = self.omit_zero(self.safe_string(order, 'stopPrice'))
|
|
if triggerPrice == '0.0':
|
|
triggerPrice = None
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': self.safe_string(order, 'orderId'),
|
|
'clientOrderId': self.safe_string(order, 'clientOrderId'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'lastUpdateTimestamp': self.safe_integer(order, 'updateTime'),
|
|
'status': self.parse_order_status(self.safe_string(order, 'status')),
|
|
'symbol': market['symbol'],
|
|
'type': self.parse_order_type(rawType),
|
|
'timeInForce': self.safe_string(order, 'timeInForce'),
|
|
'postOnly': (rawType == 'LIMIT_MAKER'),
|
|
'side': rawSideLower,
|
|
'price': self.omit_zero(self.safe_string(order, 'price')),
|
|
'triggerPrice': triggerPrice,
|
|
'cost': self.omit_zero(self.safe_string(order, 'cumulativeQuoteQty')),
|
|
'average': self.safe_string(order, 'avgPrice'),
|
|
'amount': self.safe_string(order, 'origQty'),
|
|
'filled': self.safe_string(order, 'executedQty'),
|
|
'remaining': None,
|
|
'trades': None,
|
|
'fee': None,
|
|
'marginMode': None,
|
|
'reduceOnly': None,
|
|
'leverage': None,
|
|
'hedged': None,
|
|
}, market)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'PENDING_NEW': 'open',
|
|
'NEW': 'open',
|
|
'PARTIALLY_FILLED': 'open',
|
|
'FILLED': 'closed',
|
|
'PENDING_CANCEL': 'canceled',
|
|
'CANCELED': 'canceled',
|
|
'REJECTED': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order_type(self, status):
|
|
statuses: dict = {
|
|
'MARKET': 'market',
|
|
'LIMIT': 'limit',
|
|
'LIMIT_MAKER': 'limit',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#cancel-order-trade
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#cancel-order-trade
|
|
|
|
: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>`
|
|
"""
|
|
request = {}
|
|
if self.safe_string(params, 'clientOrderId') is None:
|
|
request['orderId'] = id
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('cancelOrder', market, params, 'none')
|
|
if marketType == 'none':
|
|
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument or the "defaultType" parameter to be set to "spot" or "swap"')
|
|
response = None
|
|
if marketType == 'spot':
|
|
response = self.privateDeleteApiV1SpotOrder(self.extend(request, params))
|
|
else:
|
|
response = self.privateDeleteApiV1FuturesOrder(self.extend(request, params))
|
|
# response same `createOrder`
|
|
status = self.parse_order_status(self.safe_string(response, 'status'))
|
|
if status != 'open':
|
|
raise OrderNotFound(self.id + ' order ' + id + ' can not be canceled, ' + self.json(response))
|
|
return self.parse_order(response, market)
|
|
|
|
def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders in a market
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#cancel-all-open-orders-trade
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#cancel-orders-trade
|
|
|
|
:param str symbol: unified symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('cancelAllOrders', market, params, 'none')
|
|
if marketType == 'none':
|
|
raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument or the "defaultType" parameter to be set to "spot" or "swap"')
|
|
response = None
|
|
if marketType == 'spot':
|
|
response = self.privateDeleteApiV1SpotOpenOrders(self.extend(request, params))
|
|
#
|
|
# {"success":true} # always same response
|
|
#
|
|
else:
|
|
response = self.privateDeleteApiV1FuturesBatchOrders(self.extend(request, params))
|
|
#
|
|
# {"code": 200, "message":"success", "timestamp":1541161088303}
|
|
#
|
|
return [
|
|
self.safe_order({
|
|
'info': response,
|
|
}),
|
|
]
|
|
|
|
def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
|
|
"""
|
|
cancel multiple orders
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#cancel-multiple-orders-trade
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#cancel-multiple-orders-trade
|
|
|
|
:param str[] ids: order ids
|
|
:param str [symbol]: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
self.load_markets()
|
|
idsString = ','.join(ids)
|
|
request: dict = {
|
|
'ids': idsString,
|
|
}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('cancelOrders', market, params, 'none')
|
|
if marketType == 'none':
|
|
raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument or the "defaultType" parameter to be set to "spot" or "swap"')
|
|
response = None
|
|
if marketType == 'spot':
|
|
response = self.privateDeleteApiV1SpotCancelOrderByIds(self.extend(request, params))
|
|
#
|
|
# {"success":true} # always same response
|
|
#
|
|
else:
|
|
response = self.privateDeleteApiV1FuturesCancelOrderByIds(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code":200,
|
|
# "result":[
|
|
# {
|
|
# "orderId":"1327047813809448704",
|
|
# "code":-2013
|
|
# },
|
|
# {
|
|
# "orderId":"1327047814212101888",
|
|
# "code":-2013
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
# or empty array if no orders were canceled
|
|
result = self.safe_list(response, 'result', [])
|
|
return self.parse_orders(result, market)
|
|
|
|
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#query-order-user_data
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-order-user_data
|
|
|
|
: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()
|
|
request: dict = {
|
|
'orderId': id,
|
|
}
|
|
market = self.market(symbol)
|
|
response = None
|
|
if market['spot']:
|
|
response = self.privateGetApiV1SpotOrder(self.extend(request, params))
|
|
else:
|
|
response = self.privateGetApiV1FuturesOrder(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "time": "1756140208069",
|
|
# "updateTime": "1756140208078",
|
|
# "orderId": "2025045271033977089",
|
|
# "clientOrderId": "17561402075722006",
|
|
# "symbol": "ETHUSDT",
|
|
# "price": "3000",
|
|
# "origQty": "0.002",
|
|
# "executedQty": "0",
|
|
# "avgPrice": "0",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "timeInForce": "GTC",
|
|
# "status": "NEW",
|
|
# "accountId": "1783404067076253952", # only in SPOT
|
|
# "exchangeId": "301", # only in SPOT
|
|
# "symbolName": "ETHUSDT", # only in SPOT
|
|
# "cummulativeQuoteQty": "0", # only in SPOT
|
|
# "cumulativeQuoteQty": "0", # only in SPOT
|
|
# "stopPrice": "0.0", # only in SPOT
|
|
# "icebergQty": "0.0", # only in SPOT
|
|
# "isWorking": True # only in SPOT
|
|
# "leverage": "2", # only in CONTRACT
|
|
# "marginLocked": "9.5", # only in CONTRACT
|
|
# "priceType": "INPUT" # only in CONTRACT
|
|
# }
|
|
#
|
|
return self.parse_order(response, market)
|
|
|
|
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple orders made by the user
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#current-open-orders-user_data
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-current-open-order-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>`
|
|
"""
|
|
self.load_markets()
|
|
request = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('fetchOrders', market, params)
|
|
response = None
|
|
if marketType == 'spot':
|
|
response = self.privateGetApiV1SpotOpenOrders(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "accountId": "1783404067076253952",
|
|
# "exchangeId": "301",
|
|
# "symbol": "ETHUSDT",
|
|
# "symbolName": "ETHUSDT",
|
|
# "clientOrderId": "17561415157172008",
|
|
# "orderId": "2025056244339984384",
|
|
# "price": "3000",
|
|
# "origQty": "0.002",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "cumulativeQuoteQty": "0",
|
|
# "avgPrice": "0",
|
|
# "status": "NEW",
|
|
# "timeInForce": "GTC",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "stopPrice": "0.0",
|
|
# "icebergQty": "0.0",
|
|
# "time": "1756141516189",
|
|
# "updateTime": "1756141516198",
|
|
# "isWorking": True
|
|
# }, ...
|
|
# ]
|
|
#
|
|
else:
|
|
response = self.privateGetApiV1FuturesOpenOrders(self.extend(request, params))
|
|
return self.parse_orders(response, market, since, limit)
|
|
|
|
def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple orders made by the user
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#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>`
|
|
"""
|
|
self.load_markets()
|
|
request = {}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('fetchOrders', market, params)
|
|
response = None
|
|
if marketType == 'spot':
|
|
response = self.privateGetApiV1SpotTradeOrders(request)
|
|
#
|
|
# [
|
|
# {
|
|
# "accountId": "1783404067076253952",
|
|
# "exchangeId": "301",
|
|
# "symbol": "ETHUSDT",
|
|
# "symbolName": "ETHUSDT",
|
|
# "clientOrderId": "17561415157172008",
|
|
# "orderId": "2025056244339984384",
|
|
# "price": "3000",
|
|
# "origQty": "0.002",
|
|
# "executedQty": "0",
|
|
# "cummulativeQuoteQty": "0",
|
|
# "cumulativeQuoteQty": "0",
|
|
# "avgPrice": "0",
|
|
# "status": "NEW",
|
|
# "timeInForce": "GTC",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY",
|
|
# "stopPrice": "0.0",
|
|
# "icebergQty": "0.0",
|
|
# "time": "1756141516189",
|
|
# "updateTime": "1756141516198",
|
|
# "isWorking": True
|
|
# }, ...
|
|
# ]
|
|
#
|
|
else:
|
|
raise NotSupported(self.id + ' fetchOrders() is not supported for ' + marketType + ' markets')
|
|
return self.parse_orders(response, market, since, limit)
|
|
|
|
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple closed orders made by the user
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-history-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>`
|
|
"""
|
|
# returns the most recent closed or canceled orders up to circa two weeks ago
|
|
self.load_markets()
|
|
request = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('fetchClosedOrders', market, params)
|
|
response = None
|
|
if marketType == 'spot':
|
|
raise NotSupported(self.id + ' fetchOrders() is not supported for ' + marketType + ' markets')
|
|
else:
|
|
response = self.privateGetApiV1FuturesHistoryOrders(request)
|
|
#
|
|
# [
|
|
# {
|
|
# "time": "1756756879360",
|
|
# "updateTime": "1756757165956",
|
|
# "orderId": "2030218284767504128",
|
|
# "clientOrderId": "1756756876002",
|
|
# "symbol": "SOL-SWAP-USDT",
|
|
# "price": "144",
|
|
# "leverage": "50",
|
|
# "origQty": "1",
|
|
# "executedQty": "0",
|
|
# "executeQty": "0",
|
|
# "avgPrice": "0",
|
|
# "marginLocked": "0",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY_OPEN",
|
|
# "timeInForce": "GTC",
|
|
# "status": "CANCELED",
|
|
# "priceType": "INPUT",
|
|
# "triggerType": "0",
|
|
# "fallType": "0",
|
|
# "activeStatus": "0"
|
|
# }
|
|
# ]
|
|
#
|
|
ordersList = []
|
|
for i in range(0, len(response)):
|
|
ordersList.append({'result': response[i]})
|
|
return self.parse_orders(ordersList, market, since, limit)
|
|
|
|
def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#account-trade-list-user_data
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#account-trade-list-user_data
|
|
|
|
: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 trade 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 trades for
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
|
|
self.load_markets()
|
|
request: dict = {}
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
market = self.market(symbol)
|
|
request['symbol'] = market['id']
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('fetchMyTrades', market, params)
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
response = None
|
|
if marketType == 'spot':
|
|
response = self.privateGetApiV1AccountTrades(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id": "2024934575206059008",
|
|
# "symbol": "ETHUSDT",
|
|
# "symbolName": "ETHUSDT",
|
|
# "orderId": "2024934575097029888",
|
|
# "price": "4641.21",
|
|
# "qty": "0.001",
|
|
# "commission": "0.00464121",
|
|
# "commissionAsset": "USDT",
|
|
# "time": "1756127012094",
|
|
# "isBuyer": False,
|
|
# "isMaker": False,
|
|
# "fee": {
|
|
# "feeCoinId": "USDT",
|
|
# "feeCoinName": "USDT",
|
|
# "fee": "0.00464121"
|
|
# },
|
|
# "feeCoinId": "USDT",
|
|
# "feeAmount": "0.00464121",
|
|
# "makerRebate": "0",
|
|
# "ticketId": "4864450547563401875"
|
|
# }, ...
|
|
#
|
|
else:
|
|
response = self.privateGetApiV1FuturesUserTrades(request)
|
|
#
|
|
# [
|
|
# {
|
|
# "time": "1756758426899",
|
|
# "id": "2030231266499116032",
|
|
# "orderId": "2030231266373265152",
|
|
# "symbol": "DOGE-SWAP-USDT",
|
|
# "price": "0.21191",
|
|
# "qty": "63",
|
|
# "commissionAsset": "USDT",
|
|
# "commission": "0.00801019",
|
|
# "makerRebate": "0",
|
|
# "type": "LIMIT",
|
|
# "side": "BUY_OPEN",
|
|
# "realizedPnl": "0",
|
|
# "ticketId": "4900760819871364854",
|
|
# "isMaker": False
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_trades(response, market, since, limit)
|
|
|
|
def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
|
|
"""
|
|
transfer currency internally between wallets on the same account
|
|
|
|
https://open.big.one/docs/spot_transfer.html#transfer-of-user
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: amount to transfer
|
|
:param str fromAccount: 'spot', 'swap'
|
|
:param str toAccount: 'spot', 'swap'
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
|
|
"""
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
accountsByType = self.safe_dict(self.options, 'accountsByType', {})
|
|
fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
|
|
toId = self.safe_string(accountsByType, toAccount, toAccount)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
'quantity': self.currency_to_precision(code, amount),
|
|
'fromAccountType': fromId,
|
|
'toAccountType': toId,
|
|
}
|
|
response = self.privatePostApiV1SubAccountTransfer(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 200, # 200 = success
|
|
# "msg": "success" # response message
|
|
# }
|
|
#
|
|
return self.parse_transfer(response, currency)
|
|
|
|
def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
|
|
#
|
|
# {
|
|
# "code": 200, # 200 = success
|
|
# "msg": "success" # response message
|
|
# }
|
|
#
|
|
return {
|
|
'info': transfer,
|
|
'id': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'currency': None,
|
|
'amount': None,
|
|
'fromAccount': None,
|
|
'toAccount': None,
|
|
'status': None,
|
|
}
|
|
|
|
def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
|
|
"""
|
|
fetch the history of changes, actions done by the user or operations that altered the balance of the user
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#get-account-transaction-history-list-user_data
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#get-future-account-transaction-history-list-user_data
|
|
|
|
:param str [code]: unified currency code, default is None
|
|
:param int [since]: timestamp in ms of the earliest ledger entry, default is None
|
|
:param int [limit]: max number of ledger entries to return, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: end time in ms
|
|
:returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
|
|
"""
|
|
self.load_markets()
|
|
currency = None
|
|
request: dict = {}
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['coin'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
marketType = None
|
|
marketType, params = self.handle_market_type_and_params('cancelAllOrders', None, params)
|
|
response = None
|
|
if marketType == 'spot':
|
|
response = self.privateGetApiV1AccountBalanceFlow(self.extend(request, params))
|
|
else:
|
|
response = self.privateGetApiV1FuturesBalanceFlow(self.extend(request, params))
|
|
#
|
|
# both answers are same format
|
|
#
|
|
# [
|
|
# {
|
|
# "id": "539870570957903104",
|
|
# "accountId": "122216245228131",
|
|
# "coin": "BTC",
|
|
# "coinId": "BTC",
|
|
# "coinName": "BTC",
|
|
# "flowTypeValue": 51,
|
|
# "flowType": "USER_ACCOUNT_TRANSFER",
|
|
# "flowName": "Transfer",
|
|
# "change": "-12.5",
|
|
# "total": "379.624059937852365",
|
|
# "created": "1579093587214"
|
|
# },
|
|
#
|
|
return self.parse_ledger(response, currency, since, limit)
|
|
|
|
def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
|
|
currencyId = self.safe_string(item, 'coinId')
|
|
currency = self.safe_currency(currencyId, currency)
|
|
timestamp = self.safe_integer(item, 'created')
|
|
after = self.safe_number(item, 'total')
|
|
amountRaw = self.safe_string(item, 'change')
|
|
amount = self.parse_number(Precise.string_abs(amountRaw))
|
|
direction = 'in'
|
|
if amountRaw.startswith('-'):
|
|
direction = 'out'
|
|
return self.safe_ledger_entry({
|
|
'info': item,
|
|
'id': self.safe_string(item, 'id'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'direction': direction,
|
|
'account': None,
|
|
'referenceId': None,
|
|
'referenceAccount': None,
|
|
'type': self.parse_ledger_type(self.safe_string(item, 'flowType')),
|
|
'currency': currency['code'],
|
|
'amount': amount,
|
|
'before': None,
|
|
'after': after,
|
|
'status': None,
|
|
'fee': None,
|
|
}, currency)
|
|
|
|
def parse_ledger_type(self, type):
|
|
types: dict = {
|
|
'USER_ACCOUNT_TRANSFER': 'transfer',
|
|
'AIRDROP': 'rebate',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
def fetch_trading_fees(self, params={}) -> TradingFees:
|
|
"""
|
|
fetch the trading fees for multiple markets
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#user-trade-fee-rate-user_data
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
|
|
"""
|
|
self.load_markets()
|
|
response = None
|
|
marketType = None
|
|
market = None
|
|
marketType, params = self.handle_market_type_and_params('fetchTradingFees', None, params)
|
|
if marketType == 'spot':
|
|
raise NotSupported(self.id + ' fetchTradingFees(): does not support ' + marketType + ' markets')
|
|
elif self.in_array(marketType, ['swap', 'future']):
|
|
symbol: Str = None
|
|
symbol, params = self.handle_param_string(params, 'symbol')
|
|
if symbol is None:
|
|
raise BadRequest(self.id + ' fetchTradingFees requires a params["symbol"]')
|
|
market = self.market(symbol)
|
|
request = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = self.privateGetApiV1FuturesCommissionRate(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "openMakerFee": "0.000006", # The trade fee rate for opening pending orders
|
|
# "openTakerFee": "0.0001", # The trade fee rate for open position taker
|
|
# "closeMakerFee": "0.0002", # The trade fee rate for closing pending orders
|
|
# "closeTakerFee": "0.0004" # The trade fee rate for closing a taker order
|
|
# }
|
|
#
|
|
result: dict = {}
|
|
entry = response
|
|
marketId = self.safe_string(entry, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
fee = self.parse_trading_fee(entry, market)
|
|
result[market['symbol']] = fee
|
|
return result
|
|
|
|
def parse_trading_fee(self, data, market: Market = None):
|
|
marketId = self.safe_string(data, 'symbol')
|
|
return {
|
|
'info': data,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'maker': self.safe_number(data, 'closeMakerFee'),
|
|
'taker': self.safe_number(data, 'closeTakerFee'),
|
|
'percentage': None,
|
|
'tierBased': None,
|
|
}
|
|
|
|
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all deposits made to an account
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#deposit-history-user_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 deposit 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>`
|
|
"""
|
|
return self.fetch_deposits_or_withdrawals_helper('deposits', code, since, limit, params)
|
|
|
|
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all withdrawals made from an account
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#withdrawal-records-user_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 withdrawal 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>`
|
|
"""
|
|
return self.fetch_deposits_or_withdrawals_helper('withdrawals', code, since, limit, params)
|
|
|
|
def fetch_deposits_or_withdrawals_helper(self, type, code, since, limit, params):
|
|
self.load_markets()
|
|
currency = None
|
|
request: dict = {}
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
request['coin'] = currency['id']
|
|
if since is not None:
|
|
request['startTime'] = since
|
|
request, params = self.handle_until_option('endTime', request, params)
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = None
|
|
if type == 'deposits':
|
|
response = self.privateGetApiV1AccountDepositOrders(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "time": 1499865549590,
|
|
# "id": 100234,
|
|
# "coinName": "EOS",
|
|
# "statusCode": "DEPOSIT_CAN_WITHDRAW",
|
|
# "status": "2", # 2=SUCCESS, 11=REJECT, 12=AUDIT
|
|
# "address": "deposit2bb",
|
|
# "txId": "98A3EA560C6B3336D348B6C83F0F95ECE4F1F5919E94BD006E5BF3BF264FACFC",
|
|
# "txIdUrl": "",
|
|
# "requiredConfirmTimes": "5",
|
|
# "confirmTimes": "5",
|
|
# "quantity": "1.01",
|
|
# "coin": "EOS",
|
|
# "fromAddress": "clarkkent",
|
|
# "fromAddressTag": "19029901"
|
|
# "addressTag": "19012584",
|
|
# }
|
|
# ]
|
|
#
|
|
elif type == 'withdrawals':
|
|
response = self.privateGetApiV1AccountWithdrawOrders(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "time":"1536232111669",
|
|
# "id ":"90161227158286336",
|
|
# "accountId":"517256161325920",
|
|
# "coinName":"BHC",
|
|
# "statusCode":"PROCESSING_STATUS",
|
|
# "status":3,
|
|
# "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
|
|
# "txId ":"",
|
|
# "txIdUrl ":"",
|
|
# "requiredConfirmTimes ":0, # Number of confirmation requests
|
|
# "confirmTimes ":0, # number of confirmations
|
|
# "quantity":"14", # Withdrawal amount
|
|
# "coinId ":"BHC",
|
|
# "addressExt":"address tag",
|
|
# "arriveQuantity":"14",
|
|
# "walletHandleTime":"1536232111669",
|
|
# "feeCoinId ":"BHC",
|
|
# "feeCoinName ":"BHC",
|
|
# "fee":"0.1",
|
|
# "kernelId":"", # Exclusive to BEAM and GRIN
|
|
# "isInternalTransfer": False # Whether internal transfer
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_transactions(response, currency, since, limit, params)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# fetchDeposits & fetchWithdrawals
|
|
#
|
|
# {
|
|
# "time": 1499865549590,
|
|
# "id": 100234,
|
|
# "coinName": "EOS",
|
|
# "statusCode": "DEPOSIT_CAN_WITHDRAW",
|
|
# "status": "2", # 2=SUCCESS, 11=REJECT, 12=AUDIT
|
|
# "address": "deposit2bb",
|
|
# "txId": "98A3EA560C6B3336D348B6C83F0F95ECE4F1F5919E94BD006E5BF3BF264FACFC",
|
|
# "txIdUrl": "",
|
|
# "requiredConfirmTimes": "5",
|
|
# "confirmTimes": "5",
|
|
# "quantity": "1.01",
|
|
# "coin": "EOS", # present in "fetchDeposits"
|
|
# "coinId ":"BHC", # present in "fetchWithdrawals"
|
|
# "addressTag": "19012584", # present in "fetchDeposits"
|
|
# "addressExt":"address tag", # present in "fetchWithdrawals"
|
|
# "fromAddress": "clarkkent", # present in "fetchDeposits"
|
|
# "fromAddressTag": "19029901" # present in "fetchDeposits"
|
|
# "arriveQuantity":"14", # present in "fetchWithdrawals"
|
|
# "walletHandleTime":"1536232111669",// present in "fetchWithdrawals"
|
|
# "feeCoinId ":"BHC", # present in "fetchWithdrawals"
|
|
# "feeCoinName ":"BHC", # present in "fetchWithdrawals"
|
|
# "fee":"0.1", # present in "fetchWithdrawals"
|
|
# "kernelId":"", # present in "fetchWithdrawals"
|
|
# "isInternalTransfer": False # present in "fetchWithdrawals"
|
|
# }
|
|
#
|
|
# withdraw
|
|
#
|
|
# {
|
|
# "status": 0,
|
|
# "success": True,
|
|
# "needBrokerAudit": False, # Do you need a brokerage review?
|
|
# "id": "423885103582776064",
|
|
# "refuseReason":"" # failure rejection reason
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(transaction, 'time')
|
|
currencyId = self.safe_string_2(transaction, 'coin', 'coinId')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
feeString = self.safe_string(transaction, 'fee')
|
|
feeCoin = self.safe_string(transaction, 'feeCoinName')
|
|
fee = None
|
|
if feeString is not None:
|
|
fee = {
|
|
'cost': self.parse_number(feeString),
|
|
'currency': self.safe_currency_code(feeCoin),
|
|
}
|
|
tagTo = self.safe_string_2(transaction, 'addressTag', 'addressExt')
|
|
tagFrom = self.safe_string(transaction, 'fromAddressTag')
|
|
addressTo = self.safe_string(transaction, 'address')
|
|
addressFrom = self.safe_string(transaction, 'fromAddress')
|
|
isWithdraw = ('arriveQuantity' in transaction)
|
|
type = 'withdrawal' if isWithdraw else 'deposit'
|
|
return {
|
|
'info': transaction,
|
|
'id': self.safe_string(transaction, 'id'),
|
|
'txid': self.safe_string(transaction, 'txId'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'network': None,
|
|
'address': None,
|
|
'addressTo': addressTo,
|
|
'addressFrom': addressFrom,
|
|
'tag': None,
|
|
'tagTo': tagTo,
|
|
'tagFrom': tagFrom,
|
|
'type': type,
|
|
'amount': self.safe_number(transaction, 'quantity'),
|
|
'currency': code,
|
|
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
|
|
'updated': None,
|
|
'fee': fee,
|
|
'comment': None,
|
|
'internal': None,
|
|
}
|
|
|
|
def parse_transaction_status(self, status: Str):
|
|
statuses: dict = {
|
|
'2': 'pending',
|
|
'12': 'pending',
|
|
'11': 'failed',
|
|
'3': 'ok',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#deposit-address-user_data
|
|
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'coin': currency['id'],
|
|
}
|
|
networkCode, paramsOmitted = self.handle_network_code_and_params(self.extend(request, params))
|
|
if networkCode is None:
|
|
raise ArgumentsRequired(self.id + ' fetchDepositAddress() : param["network"] is required')
|
|
request['chainType'] = self.network_code_to_id(networkCode)
|
|
response = self.privateGetApiV1AccountDepositAddress(self.extend(request, paramsOmitted))
|
|
#
|
|
# {
|
|
# "canDeposit":false,//Is it possible to recharge
|
|
# "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
|
|
# "addressExt":"address tag",
|
|
# "minQuantity":"100",//minimum amount
|
|
# "requiredConfirmTimes ":1,//Arrival confirmation number
|
|
# "canWithdrawConfirmNum ":12,//Withdrawal confirmation number
|
|
# "coinType":"ERC20_TOKEN"
|
|
# }
|
|
#
|
|
return self.parse_deposit_address(response, currency)
|
|
|
|
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
|
address = self.safe_string(depositAddress, 'address')
|
|
self.check_address(address)
|
|
return {
|
|
'info': depositAddress,
|
|
'currency': self.safe_string(currency, 'code'),
|
|
'network': None,
|
|
'address': address,
|
|
'tag': self.safe_string(depositAddress, 'addressExt'),
|
|
}
|
|
|
|
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://toobit-docs.github.io/apidocs/spot/v1/en/#withdraw-user_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: a memo for the transaction
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
self.check_address(address)
|
|
networkCode = None
|
|
networkCode, params = self.handle_network_code_and_params(params)
|
|
if networkCode is None:
|
|
raise ArgumentsRequired(self.id + ' withdraw() : param["network"] is required')
|
|
self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'coin': currency['id'],
|
|
'address': address,
|
|
'quantity': self.currency_to_precision(currency['code'], amount),
|
|
'network': networkCode,
|
|
}
|
|
if tag is not None:
|
|
request['addressExt'] = tag
|
|
response = self.privatePostApiV1AccountWithdraw(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "status": 0,
|
|
# "success": True,
|
|
# "needBrokerAudit": False, # Do you need a brokerage review?
|
|
# "id": "423885103582776064", # Withdrawal successful order id
|
|
# "refuseReason":"" # failure rejection reason
|
|
# }
|
|
#
|
|
return self.parse_transaction(response, currency)
|
|
|
|
def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
|
|
"""
|
|
set margin mode to 'cross' or 'isolated'
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#change-margin-type-trade
|
|
|
|
:param str marginMode: 'cross' or 'isolated'
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: response from the exchange
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
if market['type'] != 'swap':
|
|
raise BadSymbol(self.id + ' setMarginMode() supports swap contracts only')
|
|
marginMode = marginMode.upper()
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'marginType': marginMode,
|
|
}
|
|
response = self.privatePostApiV1FuturesMarginType(self.extend(request, params))
|
|
#
|
|
# {"code":200,"symbolId":"BTC-SWAP-USDT","marginType":"ISOLATED"}
|
|
#
|
|
return response
|
|
|
|
def set_leverage(self, leverage: int, symbol: Str = None, params={}):
|
|
"""
|
|
set the level of leverage for a market
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#change-initial-leverage-trade
|
|
|
|
: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')
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'leverage': leverage,
|
|
}
|
|
response = self.privatePostApiV1FuturesLeverage(self.extend(request, params))
|
|
#
|
|
# {"code":200,"symbolId":"BTC-SWAP-USDT","leverage":"19"}
|
|
#
|
|
return response
|
|
|
|
def fetch_leverage(self, symbol: str, params={}) -> Leverage:
|
|
"""
|
|
fetch the set leverage for a market
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#get-the-leverage-multiple-and-position-mode-user_data
|
|
|
|
:param str symbol: unified market symbol
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `leverage structure <https://docs.ccxt.com/#/?id=leverage-structure>`
|
|
"""
|
|
self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = self.privateGetApiV1FuturesAccountLeverage(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "symbol":"BTC-SWAP-USDT", #symbol
|
|
# "leverage":"20", # leverage
|
|
# "marginType":"CROSS" # CROSS;ISOLATED
|
|
# }
|
|
# ]
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_leverage(data, market)
|
|
|
|
def parse_leverage(self, leverage: dict, market: Market = None) -> Leverage:
|
|
marketId = self.safe_string(leverage, 'symbol')
|
|
leverageValue = self.safe_integer(leverage, 'leverage')
|
|
marginType = self.safe_string(leverage, 'marginType')
|
|
marginMode = 'cross' if (marginType == 'crossed') else 'isolated'
|
|
return {
|
|
'info': leverage,
|
|
'symbol': self.safe_symbol(marketId, market),
|
|
'marginMode': marginMode,
|
|
'longLeverage': leverageValue,
|
|
'shortLeverage': leverageValue,
|
|
}
|
|
|
|
def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
|
|
"""
|
|
fetch all open positions
|
|
|
|
https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-position-user_data
|
|
|
|
:param str[]|None symbols: list of unified market symbols
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `position structures <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
self.load_markets()
|
|
request = {}
|
|
market = None
|
|
if symbols is not None:
|
|
length = len(symbols)
|
|
if length > 1:
|
|
raise BadRequest(self.id + ' fetchPositions() only accepts an array with a single symbol or without symbols argument')
|
|
firstSymbol = self.safe_string(symbols, 0)
|
|
if firstSymbol is not None:
|
|
market = self.market(firstSymbol)
|
|
request['symbol'] = market['id']
|
|
response = self.privateGetApiV1FuturesPositions(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "symbol": "DOGE-SWAP-USDT",
|
|
# "side": "LONG",
|
|
# "avgPrice": "0.21191",
|
|
# "position": "63",
|
|
# "available": "63",
|
|
# "leverage": "25",
|
|
# "lastPrice": "0.20932",
|
|
# "positionValue": "13.3503",
|
|
# "flp": "0.05471",
|
|
# "margin": "0.5262",
|
|
# "marginRate": "",
|
|
# "unrealizedPnL": "-0.1701",
|
|
# "profitRate": "-0.3185",
|
|
# "realizedPnL": "-0.008",
|
|
# "minMargin": "0",
|
|
# "maxNotionalValue": "10000000",
|
|
# "markPrice": "0.20921"
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_positions(response, symbols)
|
|
|
|
def parse_position(self, position: dict, market: Market = None):
|
|
marketId = self.safe_string(position, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
side = self.safe_string_lower(position, 'side')
|
|
quantity = self.safe_string(position, 'position')
|
|
leverage = self.safe_integer(position, 'leverage')
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': self.safe_string(position, 'id'),
|
|
'symbol': market['symbol'],
|
|
'entryPrice': self.safe_string(position, 'avgPrice'),
|
|
'markPrice': self.safe_string(position, 'markPrice'),
|
|
'lastPrice': self.safe_string(position, 'lastPrice'),
|
|
'notional': self.safe_string(position, 'positionValue'),
|
|
'collateral': None,
|
|
'unrealizedPnl': self.safe_string(position, 'unrealizedPnL'),
|
|
'side': side,
|
|
'contracts': self.parse_number(quantity),
|
|
'contractSize': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'hedged': None,
|
|
'maintenanceMargin': None,
|
|
'maintenanceMarginPercentage': None,
|
|
'initialMargin': self.safe_string(position, 'margin'),
|
|
'initialMarginPercentage': None,
|
|
'leverage': leverage,
|
|
'liquidationPrice': None,
|
|
'marginRatio': None,
|
|
'marginMode': None,
|
|
'percentage': None,
|
|
})
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
url = self.urls['api'][api] + '/' + self.implode_params(path, params)
|
|
isPost = method == 'POST'
|
|
isDelete = method == 'DELETE'
|
|
extraQuery = {}
|
|
query = self.omit(params, self.extract_params(path))
|
|
if api != 'private':
|
|
# Public endpoints
|
|
if not isPost:
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
else:
|
|
self.check_required_credentials()
|
|
timestamp = self.milliseconds()
|
|
# Add timestamp to parameters for signed endpoints
|
|
extraQuery['recvWindow'] = self.safe_string(self.options, 'recvWindow', '5000')
|
|
extraQuery['timestamp'] = str(timestamp)
|
|
queryExtended = self.extend(query, extraQuery)
|
|
queryString = ''
|
|
if isPost or isDelete:
|
|
# everything else except Batch-Orders
|
|
if not isinstance(params, list):
|
|
body = self.urlencode(queryExtended)
|
|
else:
|
|
queryString = self.urlencode(extraQuery)
|
|
body = self.json(query)
|
|
else:
|
|
queryString = self.urlencode(queryExtended)
|
|
payload = queryString
|
|
if body is not None:
|
|
payload = body + payload
|
|
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'hex')
|
|
if queryString != '':
|
|
queryString += '&signature=' + signature
|
|
url += '?' + queryString
|
|
else:
|
|
body += '&signature=' + signature
|
|
headers = {
|
|
'Referrer': 'CCXT',
|
|
'X-BB-APIKEY': self.apiKey,
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
}
|
|
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 response is None:
|
|
return None
|
|
errorCode = self.safe_string(response, 'code')
|
|
message = self.safe_string(response, 'msg')
|
|
if errorCode and errorCode != '200' and errorCode != '0':
|
|
feedback = self.id + ' ' + body
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
raise ExchangeError(feedback)
|
|
return None
|