1831 lines
74 KiB
Python
1831 lines
74 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
from ccxt.async_support.base.exchange import Exchange
|
|
from ccxt.abstract.bitopro import ImplicitAPI
|
|
import hashlib
|
|
import math
|
|
from ccxt.base.types import Any, Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees, Transaction
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class bitopro(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(bitopro, self).describe(), {
|
|
'id': 'bitopro',
|
|
'name': 'BitoPro',
|
|
'countries': ['TW'], # Taiwan
|
|
'version': 'v3',
|
|
'rateLimit': 100,
|
|
'pro': True,
|
|
'has': {
|
|
'CORS': None,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'borrowMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'cancelOrders': True,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createOrder': True,
|
|
'createOrderWithTakeProfitAndStopLoss': False,
|
|
'createOrderWithTakeProfitAndStopLossWs': False,
|
|
'createReduceOnlyOrder': False,
|
|
'createStopOrder': True,
|
|
'createTriggerOrder': True,
|
|
'editOrder': False,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchClosedOrders': True,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': True,
|
|
'fetchDepositAddress': False,
|
|
'fetchDeposits': True,
|
|
'fetchDepositsWithdrawals': False,
|
|
'fetchDepositWithdrawFee': 'emulated',
|
|
'fetchDepositWithdrawFees': True,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingInterval': False,
|
|
'fetchFundingIntervals': False,
|
|
'fetchFundingRate': False,
|
|
'fetchFundingRateHistory': False,
|
|
'fetchFundingRates': False,
|
|
'fetchGreeks': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchIsolatedPositions': False,
|
|
'fetchLeverage': False,
|
|
'fetchLeverages': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchLiquidations': False,
|
|
'fetchLongShortRatio': False,
|
|
'fetchLongShortRatioHistory': False,
|
|
'fetchMarginAdjustmentHistory': False,
|
|
'fetchMarginMode': False,
|
|
'fetchMarginModes': False,
|
|
'fetchMarketLeverageTiers': False,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMarkPrices': False,
|
|
'fetchMyLiquidations': False,
|
|
'fetchMySettlementHistory': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': False,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenInterests': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOption': False,
|
|
'fetchOptionChain': False,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': False,
|
|
'fetchOrderTrades': False,
|
|
'fetchPosition': False,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': False,
|
|
'fetchPositionsForSymbol': False,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchSettlementHistory': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': False,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': True,
|
|
'fetchTransactionFees': False,
|
|
'fetchTransactions': False,
|
|
'fetchTransfer': False,
|
|
'fetchTransfers': False,
|
|
'fetchVolatilityHistory': False,
|
|
'fetchWithdrawal': True,
|
|
'fetchWithdrawals': True,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': True,
|
|
},
|
|
'timeframes': {
|
|
'1m': '1m',
|
|
'5m': '5m',
|
|
'15m': '15m',
|
|
'30m': '30m',
|
|
'1h': '1h',
|
|
'3h': '3h',
|
|
'6h': '6h',
|
|
'12h': '12h',
|
|
'1d': '1d',
|
|
'1w': '1w',
|
|
'1M': '1M',
|
|
},
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/affc6337-b95a-44bf-aacd-04f9722364f6',
|
|
'api': {
|
|
'rest': 'https://api.bitopro.com/v3',
|
|
},
|
|
'www': 'https://www.bitopro.com',
|
|
'doc': [
|
|
'https://github.com/bitoex/bitopro-offical-api-docs/blob/master/v3-1/rest-1/rest.md',
|
|
],
|
|
'fees': 'https://www.bitopro.com/fees',
|
|
},
|
|
'requiredCredentials': {
|
|
'apiKey': True,
|
|
'secret': True,
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': {
|
|
'order-book/{pair}': 1,
|
|
'tickers': 1,
|
|
'tickers/{pair}': 1,
|
|
'trades/{pair}': 1,
|
|
'provisioning/currencies': 1,
|
|
'provisioning/trading-pairs': 1,
|
|
'provisioning/limitations-and-fees': 1,
|
|
'trading-history/{pair}': 1,
|
|
'price/otc/{currency}': 1,
|
|
},
|
|
},
|
|
'private': {
|
|
'get': {
|
|
'accounts/balance': 1,
|
|
'orders/history': 1,
|
|
'orders/all/{pair}': 1,
|
|
'orders/trades/{pair}': 1,
|
|
'orders/{pair}/{orderId}': 1,
|
|
'wallet/withdraw/{currency}/{serial}': 1,
|
|
'wallet/withdraw/{currency}/id/{id}': 1,
|
|
'wallet/depositHistory/{currency}': 1,
|
|
'wallet/withdrawHistory/{currency}': 1,
|
|
'orders/open': 1,
|
|
},
|
|
'post': {
|
|
'orders/{pair}': 1 / 2, # 1200/m => 20/s => 10/20 = 1/2
|
|
'orders/batch': 20 / 3, # 90/m => 1.5/s => 10/1.5 = 20/3
|
|
'wallet/withdraw/{currency}': 10, # 60/m => 1/s => 10/1 = 10
|
|
},
|
|
'put': {
|
|
'orders': 5, # 2/s => 10/2 = 5
|
|
},
|
|
'delete': {
|
|
'orders/{pair}/{id}': 2 / 3, # 900/m => 15/s => 10/15 = 2/3
|
|
'orders/all': 5, # 2/s => 10/2 = 5
|
|
'orders/{pair}': 5, # 2/s => 10/2 = 5
|
|
},
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'tierBased': True,
|
|
'percentage': True,
|
|
'maker': self.parse_number('0.001'),
|
|
'taker': self.parse_number('0.002'),
|
|
'tiers': {
|
|
'taker': [
|
|
[self.parse_number('0'), self.parse_number('0.002')],
|
|
[self.parse_number('3000000'), self.parse_number('0.00194')],
|
|
[self.parse_number('5000000'), self.parse_number('0.0015')],
|
|
[self.parse_number('30000000'), self.parse_number('0.0014')],
|
|
[self.parse_number('300000000'), self.parse_number('0.0013')],
|
|
[self.parse_number('550000000'), self.parse_number('0.0012')],
|
|
[self.parse_number('1300000000'), self.parse_number('0.0011')],
|
|
],
|
|
'maker': [
|
|
[self.parse_number('0'), self.parse_number('0.001')],
|
|
[self.parse_number('3000000'), self.parse_number('0.00097')],
|
|
[self.parse_number('5000000'), self.parse_number('0.0007')],
|
|
[self.parse_number('30000000'), self.parse_number('0.0006')],
|
|
[self.parse_number('300000000'), self.parse_number('0.0005')],
|
|
[self.parse_number('550000000'), self.parse_number('0.0004')],
|
|
[self.parse_number('1300000000'), self.parse_number('0.0003')],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
'options': {
|
|
'networks': {
|
|
'ERC20': 'ERC20',
|
|
'ETH': 'ERC20',
|
|
'TRX': 'TRX',
|
|
'TRC20': 'TRX',
|
|
'BEP20': 'BSC',
|
|
'BSC': 'BSC',
|
|
},
|
|
'fiatCurrencies': ['TWD'], # the only fiat currency for exchange
|
|
},
|
|
'features': {
|
|
'spot': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': True, # todo implement
|
|
'stopLossPrice': False,
|
|
'takeProfitPrice': False,
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': False,
|
|
'FOK': False,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'marketBuyByCost': False,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
# todo: implement through fetchOrders
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': None,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchClosedOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'daysBack': 100000,
|
|
'daysBackCanceled': 1,
|
|
'untilDays': 10000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOHLCV': {
|
|
'limit': 1000,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'exceptions': {
|
|
'exact': {
|
|
'Unsupported currency.': BadRequest, # {"error":"Unsupported currency."}
|
|
'Unsupported order type': BadRequest, # {"error":"Unsupported order type"}
|
|
'Invalid body': BadRequest, # {"error":"Invalid body"}
|
|
'Invalid Signature': AuthenticationError, # {"error":"Invalid Signature"}
|
|
'Address not in whitelist.': BadRequest,
|
|
},
|
|
'broad': {
|
|
'Invalid amount': InvalidOrder, # {"error":"Invalid amount 0.0000000001, decimal limit is 8."}
|
|
'Balance for ': InsufficientFunds, # {"error":"Balance for eth not enough, only has 0, but ordered 0.01."}
|
|
'Invalid ': BadRequest, # {"error":"Invalid price -1."}
|
|
'Wrong parameter': BadRequest, # {"error":"Wrong parameter: from"}
|
|
},
|
|
},
|
|
'commonCurrencies': {
|
|
},
|
|
})
|
|
|
|
async def fetch_currencies(self, params={}) -> Currencies:
|
|
"""
|
|
fetches all available currencies on an exchange
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_currency_info.md
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an associative dictionary of currencies
|
|
"""
|
|
response = await self.publicGetProvisioningCurrencies(params)
|
|
currencies = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "currency":"eth",
|
|
# "withdrawFee":"0.007",
|
|
# "minWithdraw":"0.001",
|
|
# "maxWithdraw":"1000",
|
|
# "maxDailyWithdraw":"2000",
|
|
# "withdraw":true,
|
|
# "deposit":true,
|
|
# "depositConfirmation":"12"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result: dict = {}
|
|
fiatCurrencies = self.safe_list(self.options, 'fiatCurrencies', [])
|
|
for i in range(0, len(currencies)):
|
|
currency = currencies[i]
|
|
currencyId = self.safe_string(currency, 'currency')
|
|
code = self.safe_currency_code(currencyId)
|
|
deposit = self.safe_bool(currency, 'deposit')
|
|
withdraw = self.safe_bool(currency, 'withdraw')
|
|
fee = self.safe_number(currency, 'withdrawFee')
|
|
withdrawMin = self.safe_number(currency, 'minWithdraw')
|
|
withdrawMax = self.safe_number(currency, 'maxWithdraw')
|
|
limits: dict = {
|
|
'withdraw': {
|
|
'min': withdrawMin,
|
|
'max': withdrawMax,
|
|
},
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
}
|
|
isFiat = self.in_array(code, fiatCurrencies)
|
|
result[code] = {
|
|
'id': currencyId,
|
|
'code': code,
|
|
'info': currency,
|
|
'type': 'fiat' if isFiat else 'crypto',
|
|
'name': None,
|
|
'active': deposit and withdraw,
|
|
'deposit': deposit,
|
|
'withdraw': withdraw,
|
|
'fee': fee,
|
|
'precision': None,
|
|
'limits': limits,
|
|
'networks': None,
|
|
}
|
|
return result
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for bitopro
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_trading_pair_info.md
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
response = await self.publicGetProvisioningTradingPairs()
|
|
markets = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "pair":"shib_twd",
|
|
# "base":"shib",
|
|
# "quote":"twd",
|
|
# "basePrecision":"8",
|
|
# "quotePrecision":"6",
|
|
# "minLimitBaseAmount":"100000",
|
|
# "maxLimitBaseAmount":"5500000000",
|
|
# "minMarketBuyQuoteAmount":"1000",
|
|
# "orderOpenLimit":"200",
|
|
# "maintain":false,
|
|
# "orderBookQuotePrecision":"6",
|
|
# "orderBookQuoteScaleLevel":"5"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_markets(markets)
|
|
|
|
def parse_market(self, market: dict) -> Market:
|
|
active = not self.safe_bool(market, 'maintain')
|
|
id = self.safe_string(market, 'pair')
|
|
uppercaseId = id.upper()
|
|
baseId = self.safe_string(market, 'base')
|
|
quoteId = self.safe_string(market, 'quote')
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
symbol = base + '/' + quote
|
|
limits: dict = {
|
|
'amount': {
|
|
'min': self.safe_number(market, 'minLimitBaseAmount'),
|
|
'max': self.safe_number(market, 'maxLimitBaseAmount'),
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
}
|
|
return {
|
|
'id': id,
|
|
'uppercaseId': uppercaseId,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'baseId': base,
|
|
'quoteId': quote,
|
|
'settle': None,
|
|
'settleId': None,
|
|
'type': 'spot',
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'contract': False,
|
|
'linear': None,
|
|
'inverse': None,
|
|
'contractSize': None,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'limits': limits,
|
|
'precision': {
|
|
'price': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
|
|
'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'basePrecision'))),
|
|
},
|
|
'active': active,
|
|
'created': None,
|
|
'info': market,
|
|
}
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# {
|
|
# "pair":"btc_twd",
|
|
# "lastPrice":"1182449.00000000",
|
|
# "isBuyer":false,
|
|
# "priceChange24hr":"-1.99",
|
|
# "volume24hr":"9.13089740",
|
|
# "high24hr":"1226097.00000000",
|
|
# "low24hr":"1181000.00000000"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(ticker, 'pair')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = self.safe_string(market, 'symbol')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'high': self.safe_string(ticker, 'high24hr'),
|
|
'low': self.safe_string(ticker, 'low24hr'),
|
|
'bid': None,
|
|
'bidVolume': None,
|
|
'ask': None,
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': None,
|
|
'close': self.safe_string(ticker, 'lastPrice'),
|
|
'last': self.safe_string(ticker, 'lastPrice'),
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': self.safe_string(ticker, 'priceChange24hr'),
|
|
'average': None,
|
|
'baseVolume': self.safe_string(ticker, 'volume24hr'),
|
|
'quoteVolume': None,
|
|
'info': ticker,
|
|
}, market)
|
|
|
|
async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_ticker_data.md
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
}
|
|
response = await self.publicGetTickersPair(self.extend(request, params))
|
|
ticker = self.safe_dict(response, 'data', {})
|
|
#
|
|
# {
|
|
# "data":{
|
|
# "pair":"btc_twd",
|
|
# "lastPrice":"1182449.00000000",
|
|
# "isBuyer":false,
|
|
# "priceChange24hr":"-1.99",
|
|
# "volume24hr":"9.13089740",
|
|
# "high24hr":"1226097.00000000",
|
|
# "low24hr":"1181000.00000000"
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_ticker(ticker, market)
|
|
|
|
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_ticker_data.md
|
|
|
|
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.publicGetTickers()
|
|
tickers = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "pair":"xrp_twd",
|
|
# "lastPrice":"21.26110000",
|
|
# "isBuyer":false,
|
|
# "priceChange24hr":"-6.53",
|
|
# "volume24hr":"102846.47084802",
|
|
# "high24hr":"23.24460000",
|
|
# "low24hr":"21.13730000"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_tickers(tickers, symbols)
|
|
|
|
async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_orderbook_data.md
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
}
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.publicGetOrderBookPair(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "bids":[
|
|
# {
|
|
# "price":"1175271",
|
|
# "amount":"0.00022804",
|
|
# "count":1,
|
|
# "total":"0.00022804"
|
|
# }
|
|
# ],
|
|
# "asks":[
|
|
# {
|
|
# "price":"1176906",
|
|
# "amount":"0.0496",
|
|
# "count":1,
|
|
# "total":"0.0496"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_order_book(response, market['symbol'], None, 'bids', 'asks', 'price', 'amount')
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades
|
|
# {
|
|
# "timestamp":1644651458,
|
|
# "price":"1180785.00000000",
|
|
# "amount":"0.00020000",
|
|
# "isBuyer":false
|
|
# }
|
|
#
|
|
# fetchMyTrades
|
|
# {
|
|
# "tradeId":"5685030251",
|
|
# "orderId":"9669168142",
|
|
# "price":"11821.8",
|
|
# "action":"SELL",
|
|
# "baseAmount":"0.01",
|
|
# "quoteAmount":"118.218",
|
|
# "fee":"0.236436",
|
|
# "feeSymbol":"BNB",
|
|
# "isTaker":true,
|
|
# "timestamp":1644905714862,
|
|
# "createdTimestamp":1644905714862
|
|
# }
|
|
#
|
|
id = self.safe_string(trade, 'tradeId')
|
|
orderId = self.safe_string(trade, 'orderId')
|
|
timestamp = None
|
|
if id is None:
|
|
timestamp = self.safe_timestamp(trade, 'timestamp')
|
|
else:
|
|
timestamp = self.safe_integer(trade, 'timestamp')
|
|
marketId = self.safe_string(trade, 'pair')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = self.safe_string(market, 'symbol')
|
|
price = self.safe_string(trade, 'price')
|
|
type = self.safe_string_lower(trade, 'type')
|
|
side = self.safe_string_lower(trade, 'action')
|
|
if side is None:
|
|
isBuyer = self.safe_bool(trade, 'isBuyer')
|
|
if isBuyer:
|
|
side = 'buy'
|
|
else:
|
|
side = 'sell'
|
|
amount = self.safe_string(trade, 'amount')
|
|
if amount is None:
|
|
amount = self.safe_string(trade, 'baseAmount')
|
|
fee = None
|
|
feeAmount = self.safe_string(trade, 'fee')
|
|
feeSymbol = self.safe_currency_code(self.safe_string(trade, 'feeSymbol'))
|
|
if feeAmount is not None:
|
|
fee = {
|
|
'cost': feeAmount,
|
|
'currency': feeSymbol,
|
|
'rate': None,
|
|
}
|
|
isTaker = self.safe_bool(trade, 'isTaker')
|
|
takerOrMaker = None
|
|
if isTaker is not None:
|
|
if isTaker:
|
|
takerOrMaker = 'taker'
|
|
else:
|
|
takerOrMaker = 'maker'
|
|
return self.safe_trade({
|
|
'id': id,
|
|
'info': trade,
|
|
'order': orderId,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'takerOrMaker': takerOrMaker,
|
|
'type': type,
|
|
'side': side,
|
|
'price': price,
|
|
'amount': amount,
|
|
'cost': None,
|
|
'fee': fee,
|
|
}, market)
|
|
|
|
async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a particular symbol
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_trades_data.md
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
}
|
|
response = await self.publicGetTradesPair(self.extend(request, params))
|
|
trades = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "timestamp":1644651458,
|
|
# "price":"1180785.00000000",
|
|
# "amount":"0.00020000",
|
|
# "isBuyer":false
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
async def fetch_trading_fees(self, params={}) -> TradingFees:
|
|
"""
|
|
fetch the trading fees for multiple markets
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_limitations_and_fees.md
|
|
|
|
: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
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.publicGetProvisioningLimitationsAndFees(params)
|
|
tradingFeeRate = self.safe_dict(response, 'tradingFeeRate', {})
|
|
first = self.safe_value(tradingFeeRate, 0)
|
|
#
|
|
# {
|
|
# "tradingFeeRate":[
|
|
# {
|
|
# "rank":0,
|
|
# "twdVolumeSymbol":"\u003c",
|
|
# "twdVolume":"3000000",
|
|
# "bitoAmountSymbol":"\u003c",
|
|
# "bitoAmount":"7500",
|
|
# "makerFee":"0.001",
|
|
# "takerFee":"0.002",
|
|
# "makerBitoFee":"0.0008",
|
|
# "takerBitoFee":"0.0016"
|
|
# }
|
|
# ],
|
|
# "orderFeesAndLimitations":[
|
|
# {
|
|
# "pair":"BTC/TWD",
|
|
# "minimumOrderAmount":"0.0001",
|
|
# "minimumOrderAmountBase":"BTC",
|
|
# "minimumOrderNumberOfDigits":"0"
|
|
# }
|
|
# ],
|
|
# "restrictionsOfWithdrawalFees":[
|
|
# {
|
|
# "currency":"TWD",
|
|
# "fee":"15",
|
|
# "minimumTradingAmount":"100",
|
|
# "maximumTradingAmount":"1000000",
|
|
# "dailyCumulativeMaximumAmount":"2000000",
|
|
# "remarks":"",
|
|
# "protocol":""
|
|
# }
|
|
# ],
|
|
# "cryptocurrencyDepositFeeAndConfirmation":[
|
|
# {
|
|
# "currency":"TWD",
|
|
# "generalDepositFees":"0",
|
|
# "blockchainConfirmationRequired":""
|
|
# }
|
|
# ],
|
|
# "ttCheckFeesAndLimitationsLevel1":[
|
|
# {
|
|
# "currency":"TWD",
|
|
# "redeemDailyCumulativeMaximumAmount":"",
|
|
# "generateMinimumTradingAmount":"",
|
|
# "generateMaximumTradingAmount":"",
|
|
# "generateDailyCumulativeMaximumAmount":""
|
|
# }
|
|
# ],
|
|
# "ttCheckFeesAndLimitationsLevel2":[
|
|
# {
|
|
# "currency":"TWD",
|
|
# "redeemDailyCumulativeMaximumAmount":"20000000",
|
|
# "generateMinimumTradingAmount":"30",
|
|
# "generateMaximumTradingAmount":"10000000",
|
|
# "generateDailyCumulativeMaximumAmount":"10000000"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
result: dict = {}
|
|
maker = self.safe_number(first, 'makerFee')
|
|
taker = self.safe_number(first, 'takerFee')
|
|
for i in range(0, len(self.symbols)):
|
|
symbol = self.symbols[i]
|
|
result[symbol] = {
|
|
'info': first,
|
|
'symbol': symbol,
|
|
'maker': maker,
|
|
'taker': taker,
|
|
'percentage': True,
|
|
'tierBased': True,
|
|
}
|
|
return result
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
return [
|
|
self.safe_integer(ohlcv, 'timestamp'),
|
|
self.safe_number(ohlcv, 'open'),
|
|
self.safe_number(ohlcv, 'high'),
|
|
self.safe_number(ohlcv, 'low'),
|
|
self.safe_number(ohlcv, 'close'),
|
|
self.safe_number(ohlcv, 'volume'),
|
|
]
|
|
|
|
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_ohlc_data.md
|
|
|
|
: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
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
resolution = self.safe_string(self.timeframes, timeframe, timeframe)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
'resolution': resolution,
|
|
}
|
|
# we need to have a limit argument because "to" and "from" are required
|
|
if limit is None:
|
|
limit = 500
|
|
else:
|
|
limit = min(limit, 75000) # supports slightly more than 75k candles atm, but limit here to avoid errors
|
|
timeframeInSeconds = self.parse_timeframe(timeframe)
|
|
alignedSince = None
|
|
if since is None:
|
|
request['to'] = self.seconds()
|
|
request['from'] = request['to'] - (limit * timeframeInSeconds)
|
|
else:
|
|
timeframeInMilliseconds = timeframeInSeconds * 1000
|
|
alignedSince = int(math.floor(since / timeframeInMilliseconds)) * timeframeInMilliseconds
|
|
request['from'] = int(math.floor(since / 1000))
|
|
request['to'] = self.sum(request['from'], limit * timeframeInSeconds)
|
|
response = await self.publicGetTradingHistoryPair(self.extend(request, params))
|
|
data = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "timestamp":1644581100000,
|
|
# "open":"1214737",
|
|
# "high":"1215110",
|
|
# "low":"1214737",
|
|
# "close":"1215110",
|
|
# "volume":"0.08423959"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
sparse = self.parse_ohlcvs(data, market, timeframe, since, limit)
|
|
return self.insert_missing_candles(sparse, timeframeInSeconds, alignedSince, limit)
|
|
|
|
def insert_missing_candles(self, candles, distance, since, limit):
|
|
# the exchange doesn't send zero volume candles so we emulate them instead
|
|
# otherwise sending a limit arg leads to unexpected results
|
|
length = len(candles)
|
|
if length == 0:
|
|
return candles
|
|
result = []
|
|
copyFrom = candles[0]
|
|
timestamp = None
|
|
if since is None:
|
|
timestamp = copyFrom[0]
|
|
else:
|
|
timestamp = since
|
|
i = 0
|
|
candleLength = len(candles)
|
|
resultLength = 0
|
|
while((resultLength < limit) and (i < candleLength)):
|
|
candle = candles[i]
|
|
if candle[0] == timestamp:
|
|
result.append(candle)
|
|
i = self.sum(i, 1)
|
|
else:
|
|
copy = self.array_concat([], copyFrom)
|
|
copy[0] = timestamp
|
|
# set open, high, low to close
|
|
copy[1] = copy[4]
|
|
copy[2] = copy[4]
|
|
copy[3] = copy[4]
|
|
copy[5] = self.parse_number('0')
|
|
result.append(copy)
|
|
timestamp = self.sum(timestamp, distance * 1000)
|
|
resultLength = len(result)
|
|
copyFrom = result[resultLength - 1]
|
|
return result
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
#
|
|
# [{
|
|
# "currency":"twd",
|
|
# "amount":"0",
|
|
# "available":"0",
|
|
# "stake":"0",
|
|
# "tradable":true
|
|
# }]
|
|
#
|
|
result: dict = {
|
|
'info': response,
|
|
}
|
|
for i in range(0, len(response)):
|
|
balance = response[i]
|
|
currencyId = self.safe_string(balance, 'currency')
|
|
code = self.safe_currency_code(currencyId)
|
|
amount = self.safe_string(balance, 'amount')
|
|
available = self.safe_string(balance, 'available')
|
|
account: dict = {
|
|
'free': available,
|
|
'total': amount,
|
|
}
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
async def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_account_balance.md
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.privateGetAccountsBalance(params)
|
|
balances = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "currency":"twd",
|
|
# "amount":"0",
|
|
# "available":"0",
|
|
# "stake":"0",
|
|
# "tradable":true
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_balance(balances)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'-1': 'open',
|
|
'0': 'open',
|
|
'1': 'open',
|
|
'2': 'closed',
|
|
'3': 'closed',
|
|
'4': 'canceled',
|
|
'6': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, None)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# createOrder
|
|
# {
|
|
# "orderId": "2220595581",
|
|
# "timestamp": "1644896744886",
|
|
# "action": "SELL",
|
|
# "amount": "0.01",
|
|
# "price": "15000",
|
|
# "timeInForce": "GTC"
|
|
# }
|
|
#
|
|
# fetchOrder
|
|
# {
|
|
# "id":"8777138788",
|
|
# "pair":"bnb_twd",
|
|
# "price":"16000",
|
|
# "avgExecutionPrice":"0",
|
|
# "action":"SELL",
|
|
# "type":"LIMIT",
|
|
# "timestamp":1644899002598,
|
|
# "status":4,
|
|
# "originalAmount":"0.01",
|
|
# "remainingAmount":"0.01",
|
|
# "executedAmount":"0",
|
|
# "fee":"0",
|
|
# "feeSymbol":"twd",
|
|
# "bitoFee":"0",
|
|
# "total":"0",
|
|
# "seq":"BNBTWD548774666",
|
|
# "timeInForce":"GTC",
|
|
# "createdTimestamp":1644898944074,
|
|
# "updatedTimestamp":1644899002598
|
|
# }
|
|
#
|
|
id = self.safe_string_2(order, 'id', 'orderId')
|
|
timestamp = self.safe_integer_2(order, 'timestamp', 'createdTimestamp')
|
|
side = self.safe_string(order, 'action')
|
|
side = side.lower()
|
|
amount = self.safe_string_2(order, 'amount', 'originalAmount')
|
|
price = self.safe_string(order, 'price')
|
|
marketId = self.safe_string(order, 'pair')
|
|
market = self.safe_market(marketId, market, '_')
|
|
symbol = self.safe_string(market, 'symbol')
|
|
orderStatus = self.safe_string(order, 'status')
|
|
status = self.parse_order_status(orderStatus)
|
|
type = self.safe_string_lower(order, 'type')
|
|
average = self.safe_string(order, 'avgExecutionPrice')
|
|
filled = self.safe_string(order, 'executedAmount')
|
|
remaining = self.safe_string(order, 'remainingAmount')
|
|
timeInForce = self.safe_string(order, 'timeInForce')
|
|
postOnly = None
|
|
if timeInForce == 'POST_ONLY':
|
|
postOnly = True
|
|
fee = None
|
|
feeAmount = self.safe_string(order, 'fee')
|
|
feeSymbol = self.safe_currency_code(self.safe_string(order, 'feeSymbol'))
|
|
if Precise.string_gt(feeAmount, '0'):
|
|
fee = {
|
|
'currency': feeSymbol,
|
|
'cost': feeAmount,
|
|
}
|
|
return self.safe_order({
|
|
'id': id,
|
|
'clientOrderId': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': self.safe_integer(order, 'updatedTimestamp'),
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': timeInForce,
|
|
'postOnly': postOnly,
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': None,
|
|
'amount': amount,
|
|
'cost': None,
|
|
'average': average,
|
|
'filled': filled,
|
|
'remaining': remaining,
|
|
'status': status,
|
|
'fee': fee,
|
|
'trades': None,
|
|
'info': order,
|
|
}, market)
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/create_an_order.md
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market' or 'limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much of currency you want to trade in units of base currency
|
|
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param dict [params.triggerPrice]: the price at which a trigger order is triggered at
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'type': type,
|
|
'pair': market['id'],
|
|
'action': side,
|
|
'amount': self.amount_to_precision(symbol, amount),
|
|
'timestamp': self.milliseconds(),
|
|
}
|
|
orderType = type.upper()
|
|
if orderType == 'LIMIT':
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
if orderType == 'STOP_LIMIT':
|
|
request['price'] = self.price_to_precision(symbol, price)
|
|
triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
|
|
params = self.omit(params, ['triggerPrice', 'stopPrice'])
|
|
if triggerPrice is None:
|
|
raise InvalidOrder(self.id + ' createOrder() requires a triggerPrice parameter for ' + orderType + ' orders')
|
|
else:
|
|
request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
|
|
condition = self.safe_string(params, 'condition')
|
|
if condition is None:
|
|
raise InvalidOrder(self.id + ' createOrder() requires a condition parameter for ' + orderType + ' orders')
|
|
else:
|
|
request['condition'] = condition
|
|
postOnly = self.is_post_only(orderType == 'MARKET', None, params)
|
|
if postOnly:
|
|
request['timeInForce'] = 'POST_ONLY'
|
|
response = await self.privatePostOrdersPair(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "orderId": "2220595581",
|
|
# "timestamp": "1644896744886",
|
|
# "action": "SELL",
|
|
# "amount": "0.01",
|
|
# "price": "15000",
|
|
# "timeInForce": "GTC"
|
|
# }
|
|
#
|
|
return self.parse_order(response, market)
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/cancel_an_order.md
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified symbol of the market the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'id': id,
|
|
'pair': market['id'],
|
|
}
|
|
response = await self.privateDeleteOrdersPairId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "orderId":"8777138788",
|
|
# "action":"SELL",
|
|
# "timestamp":1644899002465,
|
|
# "price":"16000",
|
|
# "amount":"0.01"
|
|
# }
|
|
#
|
|
return self.parse_order(response, market)
|
|
|
|
def parse_cancel_orders(self, data):
|
|
dataKeys = list(data.keys())
|
|
orders = []
|
|
for i in range(0, len(dataKeys)):
|
|
marketId = dataKeys[i]
|
|
orderIds = data[marketId]
|
|
for j in range(0, len(orderIds)):
|
|
orders.append(self.safe_order({
|
|
'info': orderIds[j],
|
|
'id': orderIds[j],
|
|
'symbol': self.safe_symbol(marketId),
|
|
}))
|
|
return orders
|
|
|
|
async def cancel_orders(self, ids: List[str], symbol: Str = None, params={}) -> List[Order]:
|
|
"""
|
|
cancel multiple orders
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/cancel_batch_orders.md
|
|
|
|
: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>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
id = market['uppercaseId']
|
|
request: dict = {}
|
|
request[id] = ids
|
|
response = await self.privatePutOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "data":{
|
|
# "BNB_TWD":[
|
|
# "5236347105",
|
|
# "359488711"
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data')
|
|
return self.parse_cancel_orders(data)
|
|
|
|
async def cancel_all_orders(self, symbol: Str = None, params={}) -> List[Order]:
|
|
"""
|
|
cancel all open orders
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/cancel_all_orders.md
|
|
|
|
:param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
|
|
: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>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
# 'pair': market['id'], # optional
|
|
}
|
|
response = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['pair'] = market['id']
|
|
response = await self.privateDeleteOrdersPair(self.extend(request, params))
|
|
else:
|
|
response = await self.privateDeleteOrdersAll(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
#
|
|
# {
|
|
# "data":{
|
|
# "BNB_TWD":[
|
|
# "9515988421",
|
|
# "4639130027"
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_cancel_orders(data)
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_an_order_data.md
|
|
|
|
:param str id: the order id
|
|
:param str symbol: unified symbol of the market the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'orderId': id,
|
|
'pair': market['id'],
|
|
}
|
|
response = await self.privateGetOrdersPairOrderId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id":"8777138788",
|
|
# "pair":"bnb_twd",
|
|
# "price":"16000",
|
|
# "avgExecutionPrice":"0",
|
|
# "action":"SELL",
|
|
# "type":"LIMIT",
|
|
# "timestamp":1644899002598,
|
|
# "status":4,
|
|
# "originalAmount":"0.01",
|
|
# "remainingAmount":"0.01",
|
|
# "executedAmount":"0",
|
|
# "fee":"0",
|
|
# "feeSymbol":"twd",
|
|
# "bitoFee":"0",
|
|
# "total":"0",
|
|
# "seq":"BNBTWD548774666",
|
|
# "timeInForce":"GTC",
|
|
# "createdTimestamp":1644898944074,
|
|
# "updatedTimestamp":1644899002598
|
|
# }
|
|
#
|
|
return self.parse_order(response, market)
|
|
|
|
async 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://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_orders_data.md
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
# 'startTimestamp': 0,
|
|
# 'endTimestamp': 0,
|
|
# 'statusKind': '',
|
|
# 'orderId': '',
|
|
}
|
|
if since is not None:
|
|
request['startTimestamp'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.privateGetOrdersAllPair(self.extend(request, params))
|
|
orders = self.safe_list(response, 'data', [])
|
|
if orders is None:
|
|
orders = []
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "id":"2220595581",
|
|
# "pair":"bnb_twd",
|
|
# "price":"15000",
|
|
# "avgExecutionPrice":"0",
|
|
# "action":"SELL",
|
|
# "type":"LIMIT",
|
|
# "createdTimestamp":1644896744886,
|
|
# "updatedTimestamp":1644898706236,
|
|
# "status":4,
|
|
# "originalAmount":"0.01",
|
|
# "remainingAmount":"0.01",
|
|
# "executedAmount":"0",
|
|
# "fee":"0",
|
|
# "feeSymbol":"twd",
|
|
# "bitoFee":"0",
|
|
# "total":"0",
|
|
# "seq":"BNBTWD8540871774",
|
|
# "timeInForce":"GTC"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_open_orders_data.md
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch open orders for
|
|
:param int [limit]: the maximum number of open orders structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['pair'] = market['id']
|
|
response = await self.privateGetOrdersOpen(self.extend(request, params))
|
|
orders = self.safe_list(response, 'data', [])
|
|
return self.parse_orders(orders, market, since, limit)
|
|
|
|
async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple closed orders made by the user
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_orders_data.md
|
|
|
|
: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>`
|
|
"""
|
|
request: dict = {
|
|
'statusKind': 'DONE',
|
|
}
|
|
return self.fetch_orders(symbol, since, limit, self.extend(request, params))
|
|
|
|
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_trades_data.md
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trades structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'pair': market['id'],
|
|
}
|
|
response = await self.privateGetOrdersTradesPair(self.extend(request, params))
|
|
trades = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "tradeId":"5685030251",
|
|
# "orderId":"9669168142",
|
|
# "price":"11821.8",
|
|
# "action":"SELL",
|
|
# "baseAmount":"0.01",
|
|
# "quoteAmount":"118.218",
|
|
# "fee":"0.236436",
|
|
# "feeSymbol":"BNB",
|
|
# "isTaker":true,
|
|
# "timestamp":1644905714862,
|
|
# "createdTimestamp":1644905714862
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_trades(trades, market, since, limit)
|
|
|
|
def parse_transaction_status(self, status: Str):
|
|
states: dict = {
|
|
'COMPLETE': 'ok',
|
|
'INVALID': 'failed',
|
|
'PROCESSING': 'pending',
|
|
'WAIT_PROCESS': 'pending',
|
|
'FAILED': 'failed',
|
|
'EXPIRED': 'failed',
|
|
'CANCELLED': 'failed',
|
|
'EMAIL_VERIFICATION': 'pending',
|
|
'WAIT_CONFIRMATION': 'pending',
|
|
}
|
|
return self.safe_string(states, status, status)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# fetchDeposits
|
|
#
|
|
# {
|
|
# "serial": "20220214X766799",
|
|
# "timestamp": "1644833015053",
|
|
# "address": "bnb1xml62k5a9dcewgc542fha75fyxdcp0zv8eqfsh",
|
|
# "amount": "0.20000000",
|
|
# "fee": "0.00000000",
|
|
# "total": "0.20000000",
|
|
# "status": "COMPLETE",
|
|
# "txid": "A3CC4F6828CC752B9F3737F48B5826B9EC2857040CB5141D0CC955F7E53DB6D9",
|
|
# "message": "778553959",
|
|
# "protocol": "MAIN",
|
|
# "id": "2905906537"
|
|
# }
|
|
#
|
|
# fetchWithdrawals or fetchWithdraw
|
|
#
|
|
# {
|
|
# "serial": "20220215BW14069838",
|
|
# "timestamp": "1644907716044",
|
|
# "address": "TKrwMaZaGiAvtXCFT41xHuusNcs4LPWS7w",
|
|
# "amount": "8.00000000",
|
|
# "fee": "2.00000000",
|
|
# "total": "10.00000000",
|
|
# "status": "COMPLETE",
|
|
# "txid": "50bf250c71a582f40cf699fb58bab978437ea9bdf7259ff8072e669aab30c32b",
|
|
# "protocol": "TRX",
|
|
# "id": "9925310345"
|
|
# }
|
|
#
|
|
# withdraw
|
|
#
|
|
# {
|
|
# "serial": "20220215BW14069838",
|
|
# "currency": "USDT",
|
|
# "protocol": "TRX",
|
|
# "address": "TKrwMaZaGiAvtXCFT41xHuusNcs4LPWS7w",
|
|
# "amount": "8",
|
|
# "fee": "2",
|
|
# "total": "10"
|
|
# }
|
|
#
|
|
currencyId = self.safe_string(transaction, 'coin')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
timestamp = self.safe_integer(transaction, 'timestamp')
|
|
address = self.safe_string(transaction, 'address')
|
|
tag = self.safe_string(transaction, 'message')
|
|
status = self.safe_string(transaction, 'status')
|
|
networkId = self.safe_string(transaction, 'protocol')
|
|
if networkId == 'MAIN':
|
|
networkId = code
|
|
return {
|
|
'info': transaction,
|
|
'id': self.safe_string(transaction, 'serial'),
|
|
'txid': self.safe_string(transaction, 'txid'),
|
|
'type': None,
|
|
'currency': code,
|
|
'network': self.network_id_to_code(networkId),
|
|
'amount': self.safe_number(transaction, 'total'),
|
|
'status': self.parse_transaction_status(status),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'address': address,
|
|
'addressFrom': None,
|
|
'addressTo': address,
|
|
'tag': tag,
|
|
'tagFrom': None,
|
|
'tagTo': tag,
|
|
'updated': None,
|
|
'comment': None,
|
|
'internal': None,
|
|
'fee': {
|
|
'currency': code,
|
|
'cost': self.safe_number(transaction, 'fee'),
|
|
'rate': None,
|
|
},
|
|
}
|
|
|
|
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all deposits made to an account
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_deposit_invoices_data.md
|
|
|
|
:param str code: unified currency code
|
|
:param int [since]: the earliest time in ms to fetch deposits for
|
|
:param int [limit]: the maximum number of deposits structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
if code is None:
|
|
raise ArgumentsRequired(self.id + ' fetchDeposits() requires the code argument')
|
|
await self.load_markets()
|
|
currency = self.safe_currency(code)
|
|
request: dict = {
|
|
'currency': currency['id'],
|
|
# 'endTimestamp': 0,
|
|
# 'id': '',
|
|
# 'statuses': '', # 'ROCESSING,COMPLETE,INVALID,WAIT_PROCESS,CANCELLED,FAILED'
|
|
}
|
|
if since is not None:
|
|
request['startTimestamp'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.privateGetWalletDepositHistoryCurrency(self.extend(request, params))
|
|
result = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "serial":"20220214X766799",
|
|
# "timestamp":"1644833015053",
|
|
# "address":"bnb1xml62k5a9dcewgc542fha75fyxdcp0zv8eqfsh",
|
|
# "amount":"0.20000000",
|
|
# "fee":"0.00000000",
|
|
# "total":"0.20000000",
|
|
# "status":"COMPLETE",
|
|
# "txid":"A3CC4F6828CC752B9F3737F48B5826B9EC2857040CB5141D0CC955F7E53DB6D9",
|
|
# "message":"778553959",
|
|
# "protocol":"MAIN",
|
|
# "id":"2905906537"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_transactions(result, currency, since, limit, {'type': 'deposit'})
|
|
|
|
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all withdrawals made from an account
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_withdraw_invoices_data.md
|
|
|
|
:param str code: unified currency code
|
|
:param int [since]: the earliest time in ms to fetch withdrawals for
|
|
:param int [limit]: the maximum number of withdrawals structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
if code is None:
|
|
raise ArgumentsRequired(self.id + ' fetchWithdrawals() requires the code argument')
|
|
await self.load_markets()
|
|
currency = self.safe_currency(code)
|
|
request: dict = {
|
|
'currency': currency['id'],
|
|
# 'endTimestamp': 0,
|
|
# 'id': '',
|
|
# 'statuses': '', # 'PROCESSING,COMPLETE,EXPIRED,INVALID,WAIT_PROCESS,WAIT_CONFIRMATION,EMAIL_VERIFICATION,CANCELLED'
|
|
}
|
|
if since is not None:
|
|
request['startTimestamp'] = since
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.privateGetWalletWithdrawHistoryCurrency(self.extend(request, params))
|
|
result = self.safe_list(response, 'data', [])
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "serial":"20220215BW14069838",
|
|
# "timestamp":"1644907716044",
|
|
# "address":"TKrwMaZaGiAvtXCFT41xHuusNcs4LPWS7w",
|
|
# "amount":"8.00000000",
|
|
# "fee":"2.00000000",
|
|
# "total":"10.00000000",
|
|
# "status":"COMPLETE",
|
|
# "txid":"50bf250c71a582f40cf699fb58bab978437ea9bdf7259ff8072e669aab30c32b",
|
|
# "protocol":"TRX",
|
|
# "id":"9925310345"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
return self.parse_transactions(result, currency, since, limit, {'type': 'withdrawal'})
|
|
|
|
async def fetch_withdrawal(self, id: str, code: Str = None, params={}):
|
|
"""
|
|
fetch data on a currency withdrawal via the withdrawal id
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/get_an_withdraw_invoice_data.md
|
|
|
|
:param str id: withdrawal id
|
|
:param str code: unified currency code of the currency withdrawn, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
if code is None:
|
|
raise ArgumentsRequired(self.id + ' fetchWithdrawal() requires the code argument')
|
|
await self.load_markets()
|
|
currency = self.safe_currency(code)
|
|
request: dict = {
|
|
'serial': id,
|
|
'currency': currency['id'],
|
|
}
|
|
response = await self.privateGetWalletWithdrawCurrencySerial(self.extend(request, params))
|
|
result = self.safe_dict(response, 'data', {})
|
|
#
|
|
# {
|
|
# "data":{
|
|
# "serial":"20220215BW14069838",
|
|
# "address":"TKrwMaZaGiAvtXCFT41xHuusNcs4LPWS7w",
|
|
# "amount":"8.00000000",
|
|
# "fee":"2.00000000",
|
|
# "total":"10.00000000",
|
|
# "status":"COMPLETE",
|
|
# "txid":"50bf250c71a582f40cf699fb58bab978437ea9bdf7259ff8072e669aab30c32b",
|
|
# "protocol":"TRX",
|
|
# "id":"9925310345",
|
|
# "timestamp":"1644907716044"
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_transaction(result, currency)
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/private/create_an_withdraw_invoice.md
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str tag:
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
|
await self.load_markets()
|
|
self.check_address(address)
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'currency': currency['id'],
|
|
'amount': self.number_to_string(amount),
|
|
'address': address,
|
|
}
|
|
if 'network' in params:
|
|
networks = self.safe_dict(self.options, 'networks', {})
|
|
requestedNetwork = self.safe_string_upper(params, 'network')
|
|
params = self.omit(params, ['network'])
|
|
networkId = self.safe_string(networks, requestedNetwork)
|
|
if networkId is None:
|
|
raise ExchangeError(self.id + ' invalid network ' + requestedNetwork)
|
|
request['protocol'] = networkId
|
|
if tag is not None:
|
|
request['message'] = tag
|
|
response = await self.privatePostWalletWithdrawCurrency(self.extend(request, params))
|
|
result = self.safe_dict(response, 'data', {})
|
|
#
|
|
# {
|
|
# "data":{
|
|
# "serial":"20220215BW14069838",
|
|
# "currency":"USDT",
|
|
# "protocol":"TRX",
|
|
# "address":"TKrwMaZaGiAvtXCFT41xHuusNcs4LPWS7w",
|
|
# "amount":"8",
|
|
# "fee":"2",
|
|
# "total":"10"
|
|
# }
|
|
# }
|
|
#
|
|
return self.parse_transaction(result, currency)
|
|
|
|
def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
|
|
# {
|
|
# "currency":"eth",
|
|
# "withdrawFee":"0.007",
|
|
# "minWithdraw":"0.001",
|
|
# "maxWithdraw":"1000",
|
|
# "maxDailyWithdraw":"2000",
|
|
# "withdraw":true,
|
|
# "deposit":true,
|
|
# "depositConfirmation":"12"
|
|
# }
|
|
return {
|
|
'info': fee,
|
|
'withdraw': {
|
|
'fee': self.safe_number(fee, 'withdrawFee'),
|
|
'percentage': False,
|
|
},
|
|
'deposit': {
|
|
'fee': None,
|
|
'percentage': None,
|
|
},
|
|
'networks': {},
|
|
}
|
|
|
|
async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
|
|
"""
|
|
fetch deposit and withdraw fees
|
|
|
|
https://github.com/bitoex/bitopro-offical-api-docs/blob/master/api/v3/public/get_currency_info.md
|
|
|
|
:param str[]|None codes: list of unified currency codes
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a list of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.publicGetProvisioningCurrencies(params)
|
|
#
|
|
# {
|
|
# "data":[
|
|
# {
|
|
# "currency":"eth",
|
|
# "withdrawFee":"0.007",
|
|
# "minWithdraw":"0.001",
|
|
# "maxWithdraw":"1000",
|
|
# "maxDailyWithdraw":"2000",
|
|
# "withdraw":true,
|
|
# "deposit":true,
|
|
# "depositConfirmation":"12"
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_deposit_withdraw_fees(data, codes, 'currency')
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
url = '/' + self.implode_params(path, params)
|
|
query = self.omit(params, self.extract_params(path))
|
|
if headers is None:
|
|
headers = {}
|
|
headers['X-BITOPRO-API'] = 'ccxt'
|
|
if api == 'private':
|
|
self.check_required_credentials()
|
|
if method == 'POST' or method == 'PUT':
|
|
body = self.json(params)
|
|
payload = self.string_to_base64(body)
|
|
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha384)
|
|
headers['X-BITOPRO-APIKEY'] = self.apiKey
|
|
headers['X-BITOPRO-PAYLOAD'] = payload
|
|
headers['X-BITOPRO-SIGNATURE'] = signature
|
|
elif method == 'GET' or method == 'DELETE':
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
nonce = self.milliseconds()
|
|
rawData: dict = {
|
|
'nonce': nonce,
|
|
}
|
|
data = self.json(rawData)
|
|
payload = self.string_to_base64(data)
|
|
signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha384)
|
|
headers['X-BITOPRO-APIKEY'] = self.apiKey
|
|
headers['X-BITOPRO-PAYLOAD'] = payload
|
|
headers['X-BITOPRO-SIGNATURE'] = signature
|
|
elif api == 'public' and method == 'GET':
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
url = self.urls['api']['rest'] + url
|
|
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 # fallback to the default error handler
|
|
if code >= 200 and code < 300:
|
|
return None
|
|
feedback = self.id + ' ' + body
|
|
error = self.safe_string(response, 'error')
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], error, feedback)
|
|
raise ExchangeError(feedback) # unknown message
|