1228 lines
51 KiB
Python
1228 lines
51 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.bithumb import ImplicitAPI
|
|
import asyncio
|
|
import hashlib
|
|
from ccxt.base.types import Any, Balances, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction, MarketInterface
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import AuthenticationError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import InvalidAddress
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import ExchangeNotAvailable
|
|
from ccxt.base.decimal_to_precision import TRUNCATE
|
|
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
|
|
from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class bithumb(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(bithumb, self).describe(), {
|
|
'id': 'bithumb',
|
|
'name': 'Bithumb',
|
|
'countries': ['KR'], # South Korea
|
|
'rateLimit': 500,
|
|
'pro': True,
|
|
'has': {
|
|
'CORS': True,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'borrowMargin': False,
|
|
'cancelOrder': True,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createMarketOrder': True,
|
|
'createOrder': True,
|
|
'createOrderWithTakeProfitAndStopLoss': False,
|
|
'createOrderWithTakeProfitAndStopLossWs': False,
|
|
'createReduceOnlyOrder': False,
|
|
'fetchBalance': True,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': False,
|
|
'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,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': False,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenInterests': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOption': False,
|
|
'fetchOptionChain': False,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchPosition': False,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': False,
|
|
'fetchPositionsForSymbol': False,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchSettlementHistory': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTrades': True,
|
|
'fetchTransfer': False,
|
|
'fetchTransfers': False,
|
|
'fetchVolatilityHistory': False,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': True,
|
|
},
|
|
'hostname': 'bithumb.com',
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/c9e0eefb-4777-46b9-8f09-9d7f7c4af82d',
|
|
'api': {
|
|
'public': 'https://api.{hostname}/public',
|
|
'private': 'https://api.{hostname}',
|
|
},
|
|
'www': 'https://www.bithumb.com',
|
|
'doc': 'https://apidocs.bithumb.com',
|
|
'fees': 'https://en.bithumb.com/customer_support/info_fee',
|
|
},
|
|
'api': {
|
|
'public': {
|
|
'get': [
|
|
'ticker/ALL_{quoteId}',
|
|
'ticker/{baseId}_{quoteId}',
|
|
'orderbook/ALL_{quoteId}',
|
|
'orderbook/{baseId}_{quoteId}',
|
|
'transaction_history/{baseId}_{quoteId}',
|
|
'network-info',
|
|
'assetsstatus/multichain/ALL',
|
|
'assetsstatus/multichain/{currency}',
|
|
'withdraw/minimum/ALL',
|
|
'withdraw/minimum/{currency}',
|
|
'assetsstatus/ALL',
|
|
'assetsstatus/{baseId}',
|
|
'candlestick/{baseId}_{quoteId}/{interval}',
|
|
],
|
|
},
|
|
'private': {
|
|
'post': [
|
|
'info/account',
|
|
'info/balance',
|
|
'info/wallet_address',
|
|
'info/ticker',
|
|
'info/orders',
|
|
'info/user_transactions',
|
|
'info/order_detail',
|
|
'trade/place',
|
|
'trade/cancel',
|
|
'trade/btc_withdrawal',
|
|
'trade/krw_deposit',
|
|
'trade/krw_withdrawal',
|
|
'trade/market_buy',
|
|
'trade/market_sell',
|
|
'trade/stop_limit',
|
|
],
|
|
},
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'maker': self.parse_number('0.0025'),
|
|
'taker': self.parse_number('0.0025'),
|
|
},
|
|
},
|
|
'precisionMode': SIGNIFICANT_DIGITS,
|
|
# todo: update to v2 apis
|
|
'features': {
|
|
'spot': {
|
|
'sandbox': False,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': False,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': False,
|
|
'stopLossPrice': False,
|
|
'takeProfitPrice': False,
|
|
'attachedStopLossTakeProfit': None,
|
|
'timeInForce': {
|
|
'IOC': False,
|
|
'FOK': False,
|
|
'PO': False,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': False,
|
|
'leverage': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'marketBuyByCost': False,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': None,
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': 1000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': True,
|
|
},
|
|
'fetchOrders': None,
|
|
'fetchClosedOrders': None,
|
|
'fetchOHLCV': {
|
|
'limit': 1000,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
'exceptions': {
|
|
'Bad Request(SSL)': BadRequest,
|
|
'Bad Request(Bad Method)': BadRequest,
|
|
'Bad Request.(Auth Data)': AuthenticationError, # {"status": "5100", "message": "Bad Request.(Auth Data)"}
|
|
'Not Member': AuthenticationError,
|
|
'Invalid Apikey': AuthenticationError, # {"status":"5300","message":"Invalid Apikey"}
|
|
'Method Not Allowed.(Access IP)': PermissionDenied,
|
|
'Method Not Allowed.(BTC Adress)': InvalidAddress,
|
|
'Method Not Allowed.(Access)': PermissionDenied,
|
|
'Database Fail': ExchangeNotAvailable,
|
|
'Invalid Parameter': BadRequest,
|
|
'5600': ExchangeError,
|
|
'Unknown Error': ExchangeError,
|
|
'After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions': ExchangeError, # {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"}
|
|
},
|
|
'timeframes': {
|
|
'1m': '1m',
|
|
'3m': '3m',
|
|
'5m': '5m',
|
|
'10m': '10m',
|
|
'30m': '30m',
|
|
'1h': '1h',
|
|
'6h': '6h',
|
|
'12h': '12h',
|
|
'1d': '24h',
|
|
},
|
|
'options': {
|
|
'quoteCurrencies': {
|
|
'BTC': {
|
|
'limits': {
|
|
'cost': {
|
|
'min': 0.0002,
|
|
'max': 100,
|
|
},
|
|
},
|
|
},
|
|
'KRW': {
|
|
'limits': {
|
|
'cost': {
|
|
'min': 500,
|
|
'max': 5000000000,
|
|
},
|
|
},
|
|
},
|
|
'USDT': {
|
|
'limits': {
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
'commonCurrencies': {
|
|
'ALT': 'ArchLoot',
|
|
'FTC': 'FTC2',
|
|
'SOC': 'Soda Coin',
|
|
},
|
|
})
|
|
|
|
def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
|
|
# bithumb has a different type of conflict in markets, because
|
|
# their ids are the base currency(BTC for instance), so we can have
|
|
# multiple "BTC" ids representing the different markets(BTC/ETH, "BTC/DOGE", etc)
|
|
# since they're the same we just need to return one
|
|
return super(bithumb, self).safe_market(marketId, market, delimiter, 'spot')
|
|
|
|
def amount_to_precision(self, symbol, amount):
|
|
return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for bithumb
|
|
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C-all
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
result = []
|
|
quoteCurrencies = self.safe_dict(self.options, 'quoteCurrencies', {})
|
|
quotes = list(quoteCurrencies.keys())
|
|
promises = []
|
|
for i in range(0, len(quotes)):
|
|
request = {
|
|
'quoteId': quotes[i],
|
|
}
|
|
promises.append(self.publicGetTickerALLQuoteId(self.extend(request, params)))
|
|
#
|
|
# {
|
|
# "status": "0000",
|
|
# "data": {
|
|
# "ETH": {
|
|
# "opening_price": "0.05153399",
|
|
# "closing_price": "0.05145144",
|
|
# "min_price": "0.05145144",
|
|
# "max_price": "0.05160781",
|
|
# "units_traded": "6.541124172077830855",
|
|
# "acc_trade_value": "0.33705472498492329997697755",
|
|
# "prev_closing_price": "0.0515943",
|
|
# "units_traded_24H": "43.368879902677400513",
|
|
# "acc_trade_value_24H": "2.24165339555398079994373342",
|
|
# "fluctate_24H": "-0.00018203",
|
|
# "fluctate_rate_24H": "-0.35"
|
|
# },
|
|
# "XRP": {
|
|
# "opening_price": "0.00000918",
|
|
# "closing_price": "0.0000092",
|
|
# "min_price": "0.00000918",
|
|
# "max_price": "0.0000092",
|
|
# "units_traded": "6516.949363",
|
|
# "acc_trade_value": "0.0598792533602796",
|
|
# "prev_closing_price": "0.00000916",
|
|
# "units_traded_24H": "229161.50354738",
|
|
# "acc_trade_value_24H": "2.0446589371637117",
|
|
# "fluctate_24H": "0.00000049",
|
|
# "fluctate_rate_24H": "5.63"
|
|
# },
|
|
# ...
|
|
# "date": "1721675913145"
|
|
# }
|
|
# }
|
|
#
|
|
results = await asyncio.gather(*promises)
|
|
for i in range(0, len(quotes)):
|
|
quote = quotes[i]
|
|
quoteId = quote
|
|
response = results[i]
|
|
data = self.safe_dict(response, 'data')
|
|
extension = self.safe_dict(quoteCurrencies, quote, {})
|
|
currencyIds = list(data.keys())
|
|
for j in range(0, len(currencyIds)):
|
|
currencyId = currencyIds[j]
|
|
if currencyId == 'date':
|
|
continue
|
|
market = data[currencyId]
|
|
base = self.safe_currency_code(currencyId)
|
|
active = True
|
|
if isinstance(market, list):
|
|
numElements = len(market)
|
|
if numElements == 0:
|
|
active = False
|
|
entry = self.deep_extend({
|
|
'id': currencyId,
|
|
'symbol': base + '/' + quote,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': None,
|
|
'baseId': currencyId,
|
|
'quoteId': quoteId,
|
|
'settleId': None,
|
|
'type': 'spot',
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'active': active,
|
|
'contract': False,
|
|
'linear': None,
|
|
'inverse': None,
|
|
'contractSize': None,
|
|
'expiry': None,
|
|
'expiryDateTime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': int('4'),
|
|
'price': int('4'),
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {}, # set via options
|
|
},
|
|
'created': None,
|
|
'info': market,
|
|
}, extension)
|
|
result.append(entry)
|
|
return result
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
result: dict = {'info': response}
|
|
balances = self.safe_dict(response, 'data')
|
|
codes = list(self.currencies.keys())
|
|
for i in range(0, len(codes)):
|
|
code = codes[i]
|
|
account = self.account()
|
|
currency = self.currency(code)
|
|
lowerCurrencyId = self.safe_string_lower(currency, 'id')
|
|
account['total'] = self.safe_string(balances, 'total_' + lowerCurrencyId)
|
|
account['used'] = self.safe_string(balances, 'in_use_' + lowerCurrencyId)
|
|
account['free'] = self.safe_string(balances, 'available_' + lowerCurrencyId)
|
|
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://apidocs.bithumb.com/v1.2.0/reference/%EB%B3%B4%EC%9C%A0%EC%9E%90%EC%82%B0-%EC%A1%B0%ED%9A%8C
|
|
|
|
: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()
|
|
request: dict = {
|
|
'currency': 'ALL',
|
|
}
|
|
response = await self.privatePostInfoBalance(self.extend(request, params))
|
|
return self.parse_balance(response)
|
|
|
|
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://apidocs.bithumb.com/v1.2.0/reference/%ED%98%B8%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
|
|
|
|
: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 = {
|
|
'baseId': market['baseId'],
|
|
'quoteId': market['quoteId'],
|
|
}
|
|
if limit is not None:
|
|
request['count'] = limit # default 30, max 30
|
|
response = await self.publicGetOrderbookBaseIdQuoteId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "status":"0000",
|
|
# "data":{
|
|
# "timestamp":"1587621553942",
|
|
# "payment_currency":"KRW",
|
|
# "order_currency":"BTC",
|
|
# "bids":[
|
|
# {"price":"8652000","quantity":"0.0043"},
|
|
# {"price":"8651000","quantity":"0.0049"},
|
|
# {"price":"8650000","quantity":"8.4791"},
|
|
# ],
|
|
# "asks":[
|
|
# {"price":"8654000","quantity":"0.119"},
|
|
# {"price":"8655000","quantity":"0.254"},
|
|
# {"price":"8658000","quantity":"0.119"},
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
timestamp = self.safe_integer(data, 'timestamp')
|
|
return self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 'price', 'quantity')
|
|
|
|
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
|
#
|
|
# fetchTicker, fetchTickers
|
|
#
|
|
# {
|
|
# "opening_price":"227100",
|
|
# "closing_price":"228400",
|
|
# "min_price":"222300",
|
|
# "max_price":"230000",
|
|
# "units_traded":"82618.56075337",
|
|
# "acc_trade_value":"18767376138.6031",
|
|
# "prev_closing_price":"227100",
|
|
# "units_traded_24H":"151871.13484676",
|
|
# "acc_trade_value_24H":"34247610416.8974",
|
|
# "fluctate_24H":"8700",
|
|
# "fluctate_rate_24H":"3.96",
|
|
# "date":"1587710327264", # fetchTickers inject self
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer(ticker, 'date')
|
|
symbol = self.safe_symbol(None, market)
|
|
open = self.safe_string(ticker, 'opening_price')
|
|
close = self.safe_string(ticker, 'closing_price')
|
|
baseVolume = self.safe_string(ticker, 'units_traded_24H')
|
|
quoteVolume = self.safe_string(ticker, 'acc_trade_value_24H')
|
|
return self.safe_ticker({
|
|
'symbol': symbol,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'high': self.safe_string(ticker, 'max_price'),
|
|
'low': self.safe_string(ticker, 'min_price'),
|
|
'bid': self.safe_string(ticker, 'buy_price'),
|
|
'bidVolume': None,
|
|
'ask': self.safe_string(ticker, 'sell_price'),
|
|
'askVolume': None,
|
|
'vwap': None,
|
|
'open': open,
|
|
'close': close,
|
|
'last': close,
|
|
'previousClose': None,
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'baseVolume': baseVolume,
|
|
'quoteVolume': quoteVolume,
|
|
'info': 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://apidocs.bithumb.com/v1.2.0/reference/%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C-all
|
|
|
|
: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()
|
|
result: dict = {}
|
|
quoteCurrencies = self.safe_dict(self.options, 'quoteCurrencies', {})
|
|
quotes = list(quoteCurrencies.keys())
|
|
promises = []
|
|
for i in range(0, len(quotes)):
|
|
request: dict = {
|
|
'quoteId': quotes[i],
|
|
}
|
|
promises.append(self.publicGetTickerALLQuoteId(self.extend(request, params)))
|
|
responses = await asyncio.gather(*promises)
|
|
for i in range(0, len(quotes)):
|
|
quote = quotes[i]
|
|
response = responses[i]
|
|
#
|
|
# {
|
|
# "status":"0000",
|
|
# "data":{
|
|
# "BTC":{
|
|
# "opening_price":"9045000",
|
|
# "closing_price":"9132000",
|
|
# "min_price":"8938000",
|
|
# "max_price":"9168000",
|
|
# "units_traded":"4619.79967497",
|
|
# "acc_trade_value":"42021363832.5187",
|
|
# "prev_closing_price":"9041000",
|
|
# "units_traded_24H":"8793.5045804",
|
|
# "acc_trade_value_24H":"78933458515.4962",
|
|
# "fluctate_24H":"530000",
|
|
# "fluctate_rate_24H":"6.16"
|
|
# },
|
|
# "date":"1587710878669"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
timestamp = self.safe_integer(data, 'date')
|
|
tickers = self.omit(data, 'date')
|
|
currencyIds = list(tickers.keys())
|
|
for j in range(0, len(currencyIds)):
|
|
currencyId = currencyIds[j]
|
|
ticker = data[currencyId]
|
|
base = self.safe_currency_code(currencyId)
|
|
symbol = base + '/' + quote
|
|
market = self.safe_market(symbol)
|
|
ticker['date'] = timestamp
|
|
result[symbol] = self.parse_ticker(ticker, market)
|
|
return self.filter_by_array_tickers(result, 'symbol', symbols)
|
|
|
|
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://apidocs.bithumb.com/v1.2.0/reference/%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
|
|
|
|
: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 = {
|
|
'baseId': market['baseId'],
|
|
'quoteId': market['quoteId'],
|
|
}
|
|
response = await self.publicGetTickerBaseIdQuoteId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "status":"0000",
|
|
# "data":{
|
|
# "opening_price":"227100",
|
|
# "closing_price":"228400",
|
|
# "min_price":"222300",
|
|
# "max_price":"230000",
|
|
# "units_traded":"82618.56075337",
|
|
# "acc_trade_value":"18767376138.6031",
|
|
# "prev_closing_price":"227100",
|
|
# "units_traded_24H":"151871.13484676",
|
|
# "acc_trade_value_24H":"34247610416.8974",
|
|
# "fluctate_24H":"8700",
|
|
# "fluctate_rate_24H":"3.96",
|
|
# "date":"1587710327264"
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_ticker(data, market)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# [
|
|
# 1576823400000, # 기준 시간
|
|
# "8284000", # 시가
|
|
# "8286000", # 종가
|
|
# "8289000", # 고가
|
|
# "8276000", # 저가
|
|
# "15.41503692" # 거래량
|
|
# ]
|
|
#
|
|
return [
|
|
self.safe_integer(ohlcv, 0),
|
|
self.safe_number(ohlcv, 1),
|
|
self.safe_number(ohlcv, 3),
|
|
self.safe_number(ohlcv, 4),
|
|
self.safe_number(ohlcv, 2),
|
|
self.safe_number(ohlcv, 5),
|
|
]
|
|
|
|
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://apidocs.bithumb.com/v1.2.0/reference/candlestick-rest-api
|
|
|
|
: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)
|
|
request: dict = {
|
|
'baseId': market['baseId'],
|
|
'quoteId': market['quoteId'],
|
|
'interval': self.safe_string(self.timeframes, timeframe, timeframe),
|
|
}
|
|
response = await self.publicGetCandlestickBaseIdQuoteIdInterval(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "status": "0000",
|
|
# "data": {
|
|
# [
|
|
# 1576823400000, # 기준 시간
|
|
# "8284000", # 시가
|
|
# "8286000", # 종가
|
|
# "8289000", # 고가
|
|
# "8276000", # 저가
|
|
# "15.41503692" # 거래량
|
|
# ],
|
|
# [
|
|
# 1576824000000, # 기준 시간
|
|
# "8284000", # 시가
|
|
# "8281000", # 종가
|
|
# "8289000", # 고가
|
|
# "8275000", # 저가
|
|
# "6.19584467" # 거래량
|
|
# ],
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_ohlcvs(data, market, timeframe, since, limit)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades(public)
|
|
#
|
|
# {
|
|
# "transaction_date":"2020-04-23 22:21:46",
|
|
# "type":"ask",
|
|
# "units_traded":"0.0125",
|
|
# "price":"8667000",
|
|
# "total":"108337"
|
|
# }
|
|
#
|
|
# fetchOrder(private)
|
|
#
|
|
# {
|
|
# "transaction_date": "1572497603902030",
|
|
# "price": "8601000",
|
|
# "units": "0.005",
|
|
# "fee_currency": "KRW",
|
|
# "fee": "107.51",
|
|
# "total": "43005"
|
|
# }
|
|
#
|
|
# a workaround for their bug in date format, hours are not 0-padded
|
|
timestamp = None
|
|
transactionDatetime = self.safe_string(trade, 'transaction_date')
|
|
if transactionDatetime is not None:
|
|
parts = transactionDatetime.split(' ')
|
|
numParts = len(parts)
|
|
if numParts > 1:
|
|
transactionDate = parts[0]
|
|
transactionTime = parts[1]
|
|
if len(transactionTime) < 8:
|
|
transactionTime = '0' + transactionTime
|
|
timestamp = self.parse8601(transactionDate + ' ' + transactionTime)
|
|
else:
|
|
timestamp = self.safe_integer_product(trade, 'transaction_date', 0.001)
|
|
if timestamp is not None:
|
|
timestamp -= 9 * 3600000 # they report UTC + 9 hours, server in Korean timezone
|
|
type = None
|
|
side = self.safe_string(trade, 'type')
|
|
side = 'sell' if (side == 'ask') else 'buy'
|
|
id = self.safe_string(trade, 'cont_no')
|
|
market = self.safe_market(None, market)
|
|
priceString = self.safe_string(trade, 'price')
|
|
amountString = self.fix_comma_number(self.safe_string_2(trade, 'units_traded', 'units'))
|
|
costString = self.safe_string(trade, 'total')
|
|
fee = None
|
|
feeCostString = self.safe_string(trade, 'fee')
|
|
if feeCostString is not None:
|
|
feeCurrencyId = self.safe_string(trade, 'fee_currency')
|
|
feeCurrencyCode = self.common_currency_code(feeCurrencyId)
|
|
fee = {
|
|
'cost': feeCostString,
|
|
'currency': feeCurrencyCode,
|
|
}
|
|
return self.safe_trade({
|
|
'id': id,
|
|
'info': trade,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': market['symbol'],
|
|
'order': None,
|
|
'type': type,
|
|
'side': side,
|
|
'takerOrMaker': None,
|
|
'price': priceString,
|
|
'amount': amountString,
|
|
'cost': costString,
|
|
'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://apidocs.bithumb.com/v1.2.0/reference/%EC%B5%9C%EA%B7%BC-%EC%B2%B4%EA%B2%B0-%EB%82%B4%EC%97%AD
|
|
|
|
: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 = {
|
|
'baseId': market['baseId'],
|
|
'quoteId': market['quoteId'],
|
|
}
|
|
if limit is not None:
|
|
request['count'] = limit # default 20, max 100
|
|
response = await self.publicGetTransactionHistoryBaseIdQuoteId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "status":"0000",
|
|
# "data":[
|
|
# {
|
|
# "transaction_date":"2020-04-23 22:21:46",
|
|
# "type":"ask",
|
|
# "units_traded":"0.0125",
|
|
# "price":"8667000",
|
|
# "total":"108337"
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_trades(data, market, since, limit)
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%EC%A7%80%EC%A0%95%EA%B0%80-%EC%A3%BC%EB%AC%B8%ED%95%98%EA%B8%B0
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%EC%8B%9C%EC%9E%A5%EA%B0%80-%EB%A7%A4%EC%88%98%ED%95%98%EA%B8%B0
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%EC%8B%9C%EC%9E%A5%EA%B0%80-%EB%A7%A4%EB%8F%84%ED%95%98%EA%B8%B0
|
|
|
|
: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
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'order_currency': market['id'],
|
|
'payment_currency': market['quote'],
|
|
'units': amount,
|
|
}
|
|
method = 'privatePostTradePlace'
|
|
if type == 'limit':
|
|
request['price'] = price
|
|
request['type'] = 'bid' if (side == 'buy') else 'ask'
|
|
else:
|
|
method = 'privatePostTradeMarket' + self.capitalize(side)
|
|
response = await getattr(self, method)(self.extend(request, params))
|
|
id = self.safe_string(response, 'order_id')
|
|
if id is None:
|
|
raise InvalidOrder(self.id + ' createOrder() did not return an order id')
|
|
return self.safe_order({
|
|
'info': response,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'side': side,
|
|
'id': id,
|
|
}, market)
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%EA%B1%B0%EB%9E%98-%EC%A3%BC%EB%AC%B8%EB%82%B4%EC%97%AD-%EC%83%81%EC%84%B8-%EC%A1%B0%ED%9A%8C
|
|
|
|
: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 + ' fetchOrder() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'order_id': id,
|
|
'count': 1,
|
|
'order_currency': market['base'],
|
|
'payment_currency': market['quote'],
|
|
}
|
|
response = await self.privatePostInfoOrderDetail(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "status": "0000",
|
|
# "data": {
|
|
# "order_date": "1603161798539254",
|
|
# "type": "ask",
|
|
# "order_status": "Cancel",
|
|
# "order_currency": "BTC",
|
|
# "payment_currency": "KRW",
|
|
# "watch_price": "0",
|
|
# "order_price": "13344000",
|
|
# "order_qty": "0.0125",
|
|
# "cancel_date": "1603161803809993",
|
|
# "cancel_type": "사용자취소",
|
|
# "contract": [
|
|
# {
|
|
# "transaction_date": "1603161799976383",
|
|
# "price": "13344000",
|
|
# "units": "0.0015",
|
|
# "fee_currency": "KRW",
|
|
# "fee": "0",
|
|
# "total": "20016"
|
|
# }
|
|
# ],
|
|
# }
|
|
# }
|
|
#
|
|
data = self.safe_dict(response, 'data')
|
|
return self.parse_order(self.extend(data, {'order_id': id}), market)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'Pending': 'open',
|
|
'Completed': 'closed',
|
|
'Cancel': 'canceled',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
#
|
|
# fetchOrder
|
|
#
|
|
# {
|
|
# "transaction_date": "1572497603668315",
|
|
# "type": "bid",
|
|
# "order_status": "Completed", # Completed, Cancel ...
|
|
# "order_currency": "BTC",
|
|
# "payment_currency": "KRW",
|
|
# "watch_price": "0", # present in Cancel order
|
|
# "order_price": "8601000",
|
|
# "order_qty": "0.007",
|
|
# "cancel_date": "", # filled in Cancel order
|
|
# "cancel_type": "", # filled in Cancel order, i.e. 사용자취소
|
|
# "contract": [
|
|
# {
|
|
# "transaction_date": "1572497603902030",
|
|
# "price": "8601000",
|
|
# "units": "0.005",
|
|
# "fee_currency": "KRW",
|
|
# "fee": "107.51",
|
|
# "total": "43005"
|
|
# },
|
|
# ]
|
|
# }
|
|
#
|
|
# fetchOpenOrders
|
|
#
|
|
# {
|
|
# "order_currency": "BTC",
|
|
# "payment_currency": "KRW",
|
|
# "order_id": "C0101000007408440032",
|
|
# "order_date": "1571728739360570",
|
|
# "type": "bid",
|
|
# "units": "5.0",
|
|
# "units_remaining": "5.0",
|
|
# "price": "501000",
|
|
# }
|
|
#
|
|
timestamp = self.safe_integer_product(order, 'order_date', 0.001)
|
|
sideProperty = self.safe_string_2(order, 'type', 'side')
|
|
side = 'buy' if (sideProperty == 'bid') else 'sell'
|
|
status = self.parse_order_status(self.safe_string(order, 'order_status'))
|
|
price = self.safe_string_2(order, 'order_price', 'price')
|
|
type = 'limit'
|
|
if Precise.string_equals(price, '0'):
|
|
type = 'market'
|
|
amount = self.fix_comma_number(self.safe_string_2(order, 'order_qty', 'units'))
|
|
remaining = self.fix_comma_number(self.safe_string(order, 'units_remaining'))
|
|
if remaining is None:
|
|
if status == 'closed':
|
|
remaining = '0'
|
|
elif status != 'canceled':
|
|
remaining = amount
|
|
symbol = None
|
|
baseId = self.safe_string(order, 'order_currency')
|
|
quoteId = self.safe_string(order, 'payment_currency')
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
if (base is not None) and (quote is not None):
|
|
symbol = base + '/' + quote
|
|
if symbol is None:
|
|
market = self.safe_market(None, market)
|
|
symbol = market['symbol']
|
|
id = self.safe_string(order, 'order_id')
|
|
rawTrades = self.safe_list(order, 'contract', [])
|
|
return self.safe_order({
|
|
'info': order,
|
|
'id': id,
|
|
'clientOrderId': None,
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'lastTradeTimestamp': None,
|
|
'symbol': symbol,
|
|
'type': type,
|
|
'timeInForce': None,
|
|
'postOnly': None,
|
|
'side': side,
|
|
'price': price,
|
|
'triggerPrice': None,
|
|
'amount': amount,
|
|
'cost': None,
|
|
'average': None,
|
|
'filled': None,
|
|
'remaining': remaining,
|
|
'status': status,
|
|
'fee': None,
|
|
'trades': rawTrades,
|
|
}, market)
|
|
|
|
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%EA%B1%B0%EB%9E%98-%EC%A3%BC%EB%AC%B8%EB%82%B4%EC%97%AD-%EC%A1%B0%ED%9A%8C
|
|
|
|
:param str symbol: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch open orders for
|
|
:param int [limit]: the maximum number of open order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
if limit is None:
|
|
limit = 100
|
|
request: dict = {
|
|
'count': limit,
|
|
'order_currency': market['base'],
|
|
'payment_currency': market['quote'],
|
|
}
|
|
if since is not None:
|
|
request['after'] = since
|
|
response = await self.privatePostInfoOrders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "status": "0000",
|
|
# "data": [
|
|
# {
|
|
# "order_currency": "BTC",
|
|
# "payment_currency": "KRW",
|
|
# "order_id": "C0101000007408440032",
|
|
# "order_date": "1571728739360570",
|
|
# "type": "bid",
|
|
# "units": "5.0",
|
|
# "units_remaining": "5.0",
|
|
# "price": "501000",
|
|
# }
|
|
# ]
|
|
# }
|
|
#
|
|
data = self.safe_list(response, 'data', [])
|
|
return self.parse_orders(data, market, since, limit)
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%EC%A3%BC%EB%AC%B8-%EC%B7%A8%EC%86%8C%ED%95%98%EA%B8%B0
|
|
|
|
: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')
|
|
side_in_params = ('side' in params)
|
|
if not side_in_params:
|
|
raise ArgumentsRequired(self.id + ' cancelOrder() requires a `side` parameter(sell or buy)')
|
|
market = self.market(symbol)
|
|
side = 'bid' if (params['side'] == 'buy') else 'ask'
|
|
params = self.omit(params, ['side', 'currency'])
|
|
# https://github.com/ccxt/ccxt/issues/6771
|
|
request: dict = {
|
|
'order_id': id,
|
|
'type': side,
|
|
'order_currency': market['base'],
|
|
'payment_currency': market['quote'],
|
|
}
|
|
response = await self.privatePostTradeCancel(self.extend(request, params))
|
|
#
|
|
# {
|
|
# 'status': 'string',
|
|
# }
|
|
#
|
|
return self.safe_order({
|
|
'info': response,
|
|
})
|
|
|
|
async def cancel_unified_order(self, order: Order, params={}):
|
|
request: dict = {
|
|
'side': order['side'],
|
|
}
|
|
return await self.cancel_order(order['id'], order['symbol'], self.extend(request, params))
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://apidocs.bithumb.com/v1.2.0/reference/%EC%BD%94%EC%9D%B8-%EC%B6%9C%EA%B8%88%ED%95%98%EA%B8%B0-%EA%B0%9C%EC%9D%B8
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str tag:
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
|
self.check_address(address)
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'units': amount,
|
|
'address': address,
|
|
'currency': currency['id'],
|
|
}
|
|
if code == 'XRP' or code == 'XMR' or code == 'EOS' or code == 'STEEM' or code == 'TON':
|
|
destination = self.safe_string(params, 'destination')
|
|
if (tag is None) and (destination is None):
|
|
raise ArgumentsRequired(self.id + ' ' + code + ' withdraw() requires a tag argument or an extra destination param')
|
|
elif tag is not None:
|
|
request['destination'] = tag
|
|
response = await self.privatePostTradeBtcWithdrawal(self.extend(request, params))
|
|
#
|
|
# {"status" : "0000"}
|
|
#
|
|
return self.parse_transaction(response, currency)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# withdraw
|
|
#
|
|
# {"status" : "0000"}
|
|
#
|
|
currency = self.safe_currency(None, currency)
|
|
return {
|
|
'id': None,
|
|
'txid': None,
|
|
'timestamp': None,
|
|
'datetime': None,
|
|
'network': None,
|
|
'addressFrom': None,
|
|
'address': None,
|
|
'addressTo': None,
|
|
'amount': None,
|
|
'type': None,
|
|
'currency': currency['code'],
|
|
'status': None,
|
|
'updated': None,
|
|
'tagFrom': None,
|
|
'tag': None,
|
|
'tagTo': None,
|
|
'comment': None,
|
|
'internal': None,
|
|
'fee': None,
|
|
'info': transaction,
|
|
}
|
|
|
|
def fix_comma_number(self, numberStr):
|
|
# some endpoints need self https://github.com/ccxt/ccxt/issues/11031
|
|
if numberStr is None:
|
|
return None
|
|
finalNumberStr = numberStr
|
|
while(finalNumberStr.find(',') > -1):
|
|
finalNumberStr = finalNumberStr.replace(',', '')
|
|
return finalNumberStr
|
|
|
|
def nonce(self):
|
|
return self.milliseconds()
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
endpoint = '/' + self.implode_params(path, params)
|
|
url = self.implode_hostname(self.urls['api'][api]) + endpoint
|
|
query = self.omit(params, self.extract_params(path))
|
|
if api == 'public':
|
|
if query:
|
|
url += '?' + self.urlencode(query)
|
|
else:
|
|
self.check_required_credentials()
|
|
body = self.urlencode(self.extend({
|
|
'endpoint': endpoint,
|
|
}, query))
|
|
nonce = str(self.nonce())
|
|
auth = endpoint + "\0" + body + "\0" + nonce # eslint-disable-line quotes
|
|
signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha512)
|
|
signature64 = self.string_to_base64(signature)
|
|
headers = {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'Api-Key': self.apiKey,
|
|
'Api-Sign': signature64,
|
|
'Api-Nonce': nonce,
|
|
}
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
if response is None:
|
|
return None # fallback to default error handler
|
|
if 'status' in response:
|
|
#
|
|
# {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"}
|
|
#
|
|
status = self.safe_string(response, 'status')
|
|
message = self.safe_string(response, 'message')
|
|
if status is not None:
|
|
if status == '0000':
|
|
return None # no error
|
|
elif message == '거래 진행중인 내역이 존재하지 않습니다.':
|
|
# https://github.com/ccxt/ccxt/issues/9017
|
|
return None # no error
|
|
feedback = self.id + ' ' + message
|
|
self.throw_exactly_matched_exception(self.exceptions, status, feedback)
|
|
self.throw_exactly_matched_exception(self.exceptions, message, feedback)
|
|
raise ExchangeError(feedback)
|
|
return None
|