2247 lines
99 KiB
Python
2247 lines
99 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
||
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
||
|
||
from ccxt.base.exchange import Exchange
|
||
from ccxt.abstract.upbit import ImplicitAPI
|
||
from ccxt.base.types import Any, Balances, Currency, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, OrderBooks, Trade, TradingFeeInterface, TradingFees, Transaction
|
||
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 InsufficientFunds
|
||
from ccxt.base.errors import AddressPending
|
||
from ccxt.base.errors import InvalidOrder
|
||
from ccxt.base.errors import OrderNotFound
|
||
from ccxt.base.decimal_to_precision import TICK_SIZE
|
||
from ccxt.base.precise import Precise
|
||
|
||
|
||
class upbit(Exchange, ImplicitAPI):
|
||
|
||
def describe(self) -> Any:
|
||
return self.deep_extend(super(upbit, self).describe(), {
|
||
'id': 'upbit',
|
||
'name': 'Upbit',
|
||
'countries': ['KR'],
|
||
'version': 'v1',
|
||
'rateLimit': 50,
|
||
'pro': True,
|
||
# new metainfo interface
|
||
'has': {
|
||
'CORS': True,
|
||
'spot': True,
|
||
'margin': None,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': False,
|
||
'cancelOrder': True,
|
||
'createDepositAddress': True,
|
||
'createMarketBuyOrderWithCost': True,
|
||
'createMarketOrder': True,
|
||
'createMarketOrderWithCost': False,
|
||
'createMarketSellOrderWithCost': False,
|
||
'createOrder': True,
|
||
'editOrder': True,
|
||
'fetchBalance': True,
|
||
'fetchCanceledOrders': True,
|
||
'fetchClosedOrders': True,
|
||
'fetchCurrencies': False,
|
||
'fetchDeposit': True,
|
||
'fetchDepositAddress': True,
|
||
'fetchDepositAddresses': True,
|
||
'fetchDepositAddressesByNetwork': False,
|
||
'fetchDeposits': True,
|
||
'fetchFundingHistory': False,
|
||
'fetchFundingRate': False,
|
||
'fetchFundingRateHistory': False,
|
||
'fetchFundingRates': False,
|
||
'fetchIndexOHLCV': False,
|
||
'fetchMarginMode': False,
|
||
'fetchMarkets': True,
|
||
'fetchMarkOHLCV': False,
|
||
'fetchMyTrades': False,
|
||
'fetchOHLCV': True,
|
||
'fetchOpenInterestHistory': False,
|
||
'fetchOpenOrders': True,
|
||
'fetchOrder': True,
|
||
'fetchOrderBook': True,
|
||
'fetchOrderBooks': True,
|
||
'fetchOrders': False,
|
||
'fetchPositionMode': False,
|
||
'fetchPremiumIndexOHLCV': False,
|
||
'fetchTicker': True,
|
||
'fetchTickers': True,
|
||
'fetchTrades': True,
|
||
'fetchTradingFee': True,
|
||
'fetchTradingFees': True,
|
||
'fetchTransactions': False,
|
||
'fetchWithdrawal': True,
|
||
'fetchWithdrawals': True,
|
||
'transfer': False,
|
||
'withdraw': True,
|
||
},
|
||
'timeframes': {
|
||
'1s': 'seconds',
|
||
'1m': 'minutes',
|
||
'3m': 'minutes',
|
||
'5m': 'minutes',
|
||
'10m': 'minutes',
|
||
'15m': 'minutes',
|
||
'30m': 'minutes',
|
||
'1h': 'minutes',
|
||
'4h': 'minutes',
|
||
'1d': 'days',
|
||
'1w': 'weeks',
|
||
'1M': 'months',
|
||
'1y': 'years',
|
||
},
|
||
'hostname': 'api.upbit.com',
|
||
'urls': {
|
||
'logo': 'https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg',
|
||
'api': {
|
||
'public': 'https://{hostname}',
|
||
'private': 'https://{hostname}',
|
||
},
|
||
'www': 'https://upbit.com',
|
||
'doc': 'https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C',
|
||
'fees': 'https://upbit.com/service_center/guide',
|
||
},
|
||
'api': {
|
||
# 'endpoint','API Cost'
|
||
# cost = 1000 / (rateLimit * RPS)
|
||
'public': {
|
||
'get': {
|
||
'market/all': 2, # RPS: 10
|
||
'candles/{timeframe}': 2,
|
||
'candles/{timeframe}/{unit}': 2,
|
||
'candles/seconds': 2,
|
||
'candles/minutes/{unit}': 2,
|
||
'candles/minutes/1': 2,
|
||
'candles/minutes/3': 2,
|
||
'candles/minutes/5': 2,
|
||
'candles/minutes/10': 2,
|
||
'candles/minutes/15': 2,
|
||
'candles/minutes/30': 2,
|
||
'candles/minutes/60': 2,
|
||
'candles/minutes/240': 2,
|
||
'candles/days': 2,
|
||
'candles/weeks': 2,
|
||
'candles/months': 2,
|
||
'candles/years': 2,
|
||
'trades/ticks': 2,
|
||
'ticker': 2,
|
||
'ticker/all': 2,
|
||
'orderbook': 2,
|
||
'orderbook/instruments': 2,
|
||
'orderbook/supported_levels': 2, # Upbit KR only, deprecatd
|
||
},
|
||
},
|
||
'private': {
|
||
'get': {
|
||
'accounts': 0.67, # RPS: 30
|
||
'orders/chance': 0.67,
|
||
'order': 0.67,
|
||
'orders/closed': 0.67,
|
||
'orders/open': 0.67,
|
||
'orders/uuids': 0.67,
|
||
'withdraws': 0.67,
|
||
'withdraw': 0.67,
|
||
'withdraws/chance': 0.67,
|
||
'withdraws/coin_addresses': 0.67,
|
||
'deposits': 0.67,
|
||
'deposits/chance/coin': 0.67,
|
||
'deposit': 0.67,
|
||
'deposits/coin_addresses': 0.67,
|
||
'deposits/coin_address': 0.67,
|
||
'travel_rule/vasps': 0.67,
|
||
'status/wallet': 0.67, # Upbit KR only
|
||
'api_keys': 0.67, # Upbit KR only
|
||
},
|
||
'post': {
|
||
'orders': 2.5, # RPS: 8
|
||
'orders/cancel_and_new': 2.5, # RPS: 8
|
||
'withdraws/coin': 0.67,
|
||
'withdraws/krw': 0.67, # Upbit KR only.
|
||
'deposits/krw': 0.67, # Upbit KR only.
|
||
'deposits/generate_coin_address': 0.67,
|
||
'travel_rule/deposit/uuid': 0.67, # RPS: 30, but each deposit can only be queried once every 10 minutes
|
||
'travel_rule/deposit/txid': 0.67, # RPS: 30, but each deposit can only be queried once every 10 minutes
|
||
},
|
||
'delete': {
|
||
'order': 0.67,
|
||
'orders/open': 40, # RPS: 0.5
|
||
'orders/uuids': 0.67,
|
||
},
|
||
},
|
||
},
|
||
'fees': {
|
||
'trading': {
|
||
'tierBased': False,
|
||
'percentage': True,
|
||
'maker': self.parse_number('0.0025'),
|
||
'taker': self.parse_number('0.0025'),
|
||
},
|
||
'funding': {
|
||
'tierBased': False,
|
||
'percentage': False,
|
||
'withdraw': {},
|
||
'deposit': {},
|
||
},
|
||
},
|
||
'features': {
|
||
'spot': {
|
||
'sandbox': False,
|
||
'createOrder': {
|
||
'marginMode': False,
|
||
'triggerPrice': False,
|
||
'triggerPriceType': None,
|
||
'triggerDirection': False,
|
||
'stopLossPrice': False,
|
||
'takeProfitPrice': False,
|
||
'attachedStopLossTakeProfit': None,
|
||
'timeInForce': {
|
||
'IOC': True,
|
||
'FOK': True,
|
||
'PO': True,
|
||
'GTD': False,
|
||
},
|
||
'hedged': False,
|
||
'leverage': False,
|
||
'marketBuyByCost': False,
|
||
'marketBuyRequiresPrice': False,
|
||
'selfTradePrevention': True,
|
||
'trailing': False,
|
||
'iceberg': False,
|
||
},
|
||
'createOrders': None,
|
||
'fetchMyTrades': None,
|
||
'fetchOrder': {
|
||
'marginMode': False,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOpenOrders': {
|
||
'marginMode': True,
|
||
'limit': 100,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOrders': None, # todo
|
||
'fetchClosedOrders': {
|
||
'marginMode': False,
|
||
'limit': 1000,
|
||
'daysBack': 100000,
|
||
'daysBackCanceled': 1,
|
||
'untilDays': 7,
|
||
'trigger': False,
|
||
'trailing': False,
|
||
'symbolRequired': False,
|
||
},
|
||
'fetchOHLCV': {
|
||
'limit': 200,
|
||
},
|
||
},
|
||
'swap': {
|
||
'linear': None,
|
||
'inverse': None,
|
||
},
|
||
'future': {
|
||
'linear': None,
|
||
'inverse': None,
|
||
},
|
||
},
|
||
'precisionMode': TICK_SIZE,
|
||
'exceptions': {
|
||
'exact': {
|
||
'This key has expired.': AuthenticationError,
|
||
'Missing request parameter error. Check the required parameters!': BadRequest,
|
||
'side is missing, side does not have a valid value': InvalidOrder,
|
||
},
|
||
'broad': {
|
||
'thirdparty_agreement_required': PermissionDenied,
|
||
'out_of_scope': PermissionDenied,
|
||
'order_not_found': OrderNotFound,
|
||
'insufficient_funds': InsufficientFunds,
|
||
'invalid_access_key': AuthenticationError,
|
||
'jwt_verification': AuthenticationError,
|
||
'create_ask_error': ExchangeError,
|
||
'create_bid_error': ExchangeError,
|
||
'volume_too_large': InvalidOrder,
|
||
'invalid_funds': InvalidOrder,
|
||
},
|
||
},
|
||
'options': {
|
||
'createMarketBuyOrderRequiresPrice': True,
|
||
'tradingFeesByQuoteCurrency': {
|
||
'KRW': 0.0005,
|
||
},
|
||
},
|
||
'commonCurrencies': {
|
||
'TON': 'Tokamak Network',
|
||
},
|
||
})
|
||
|
||
def fetch_currency(self, code: str, params={}):
|
||
# self method is for retrieving funding fees and limits per currency
|
||
# it requires private access and API keys properly set up
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
return self.fetch_currency_by_id(currency['id'], params)
|
||
|
||
def fetch_currency_by_id(self, id: str, params={}):
|
||
# self method is for retrieving funding fees and limits per currency
|
||
# it requires private access and API keys properly set up
|
||
request: dict = {
|
||
'currency': id,
|
||
}
|
||
response = self.privateGetWithdrawsChance(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "member_level": {
|
||
# "security_level": 3,
|
||
# "fee_level": 0,
|
||
# "email_verified": True,
|
||
# "identity_auth_verified": True,
|
||
# "bank_account_verified": True,
|
||
# "kakao_pay_auth_verified": False,
|
||
# "locked": False,
|
||
# "wallet_locked": False
|
||
# },
|
||
# "currency": {
|
||
# "code": "BTC",
|
||
# "withdraw_fee": "0.0005",
|
||
# "is_coin": True,
|
||
# "wallet_state": "working",
|
||
# "wallet_support": ["deposit", "withdraw"]
|
||
# },
|
||
# "account": {
|
||
# "currency": "BTC",
|
||
# "balance": "10.0",
|
||
# "locked": "0.0",
|
||
# "avg_krw_buy_price": "8042000",
|
||
# "modified": False
|
||
# },
|
||
# "withdraw_limit": {
|
||
# "currency": "BTC",
|
||
# "minimum": null,
|
||
# "onetime": null,
|
||
# "daily": "10.0",
|
||
# "remaining_daily": "10.0",
|
||
# "remaining_daily_krw": "0.0",
|
||
# "fixed": null,
|
||
# "can_withdraw": True
|
||
# }
|
||
# }
|
||
#
|
||
memberInfo = self.safe_value(response, 'member_level', {})
|
||
currencyInfo = self.safe_value(response, 'currency', {})
|
||
withdrawLimits = self.safe_value(response, 'withdraw_limit', {})
|
||
canWithdraw = self.safe_value(withdrawLimits, 'can_withdraw')
|
||
walletState = self.safe_string(currencyInfo, 'wallet_state')
|
||
walletLocked = self.safe_value(memberInfo, 'wallet_locked')
|
||
locked = self.safe_value(memberInfo, 'locked')
|
||
active = True
|
||
if (canWithdraw is not None) and not canWithdraw:
|
||
active = False
|
||
elif walletState != 'working':
|
||
active = False
|
||
elif (walletLocked is not None) and walletLocked:
|
||
active = False
|
||
elif (locked is not None) and locked:
|
||
active = False
|
||
maxOnetimeWithdrawal = self.safe_string(withdrawLimits, 'onetime')
|
||
maxDailyWithdrawal = self.safe_string(withdrawLimits, 'daily', maxOnetimeWithdrawal)
|
||
remainingDailyWithdrawal = self.safe_string(withdrawLimits, 'remaining_daily', maxDailyWithdrawal)
|
||
maxWithdrawLimit = None
|
||
if Precise.string_gt(remainingDailyWithdrawal, '0'):
|
||
maxWithdrawLimit = remainingDailyWithdrawal
|
||
else:
|
||
maxWithdrawLimit = maxDailyWithdrawal
|
||
currencyId = self.safe_string(currencyInfo, 'code')
|
||
code = self.safe_currency_code(currencyId)
|
||
return {
|
||
'info': response,
|
||
'id': currencyId,
|
||
'code': code,
|
||
'name': code,
|
||
'active': active,
|
||
'fee': self.safe_number(currencyInfo, 'withdraw_fee'),
|
||
'precision': None,
|
||
'limits': {
|
||
'withdraw': {
|
||
'min': self.safe_number(withdrawLimits, 'minimum'),
|
||
'max': self.parse_number(maxWithdrawLimit),
|
||
},
|
||
},
|
||
}
|
||
|
||
def fetch_market(self, symbol: str, params={}):
|
||
# self method is for retrieving trading fees and limits per market
|
||
# it requires private access and API keys properly set up
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
return self.fetch_market_by_id(market['id'], params)
|
||
|
||
def fetch_market_by_id(self, id: str, params={}):
|
||
# self method is for retrieving trading fees and limits per market
|
||
# it requires private access and API keys properly set up
|
||
request: dict = {
|
||
'market': id,
|
||
}
|
||
response = self.privateGetOrdersChance(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "bid_fee": "0.0015",
|
||
# "ask_fee": "0.0015",
|
||
# "market": {
|
||
# "id": "KRW-BTC",
|
||
# "name": "BTC/KRW",
|
||
# "order_types": ["limit"],
|
||
# "order_sides": ["ask", "bid"],
|
||
# "bid": {"currency": "KRW", "price_unit": null, "min_total": 1000},
|
||
# "ask": {"currency": "BTC", "price_unit": null, "min_total": 1000},
|
||
# "max_total": "100000000.0",
|
||
# "state": "active",
|
||
# },
|
||
# "bid_account": {
|
||
# "currency": "KRW",
|
||
# "balance": "0.0",
|
||
# "locked": "0.0",
|
||
# "avg_buy_price": "0",
|
||
# "avg_buy_price_modified": False,
|
||
# "unit_currency": "KRW",
|
||
# },
|
||
# "ask_account": {
|
||
# "currency": "BTC",
|
||
# "balance": "10.0",
|
||
# "locked": "0.0",
|
||
# "avg_buy_price": "8042000",
|
||
# "avg_buy_price_modified": False,
|
||
# "unit_currency": "KRW",
|
||
# }
|
||
# }
|
||
#
|
||
marketInfo = self.safe_value(response, 'market')
|
||
bid = self.safe_value(marketInfo, 'bid')
|
||
ask = self.safe_value(marketInfo, 'ask')
|
||
marketId = self.safe_string(marketInfo, 'id')
|
||
baseId = self.safe_string(ask, 'currency')
|
||
quoteId = self.safe_string(bid, 'currency')
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
state = self.safe_string(marketInfo, 'state')
|
||
bidFee = self.safe_string(response, 'bid_fee')
|
||
askFee = self.safe_string(response, 'ask_fee')
|
||
fee = self.parse_number(Precise.string_max(bidFee, askFee))
|
||
return self.safe_market_structure({
|
||
'id': marketId,
|
||
'symbol': base + '/' + quote,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': None,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': None,
|
||
'type': 'spot',
|
||
'spot': True,
|
||
'margin': False,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': False,
|
||
'active': (state == 'active'),
|
||
'contract': False,
|
||
'linear': None,
|
||
'inverse': None,
|
||
'taker': fee,
|
||
'maker': fee,
|
||
'contractSize': None,
|
||
'expiry': None,
|
||
'expiryDatetime': None,
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'amount': self.parse_number('1e-8'),
|
||
'price': self.parse_number('1e-8'),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': self.safe_number(ask, 'min_total'),
|
||
'max': None,
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': self.safe_number(bid, 'min_total'),
|
||
'max': self.safe_number(marketInfo, 'max_total'),
|
||
},
|
||
'info': response,
|
||
},
|
||
})
|
||
|
||
def fetch_markets(self, params={}) -> List[Market]:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/마켓-코드-조회
|
||
https://global-docs.upbit.com/reference/listing-market-list
|
||
|
||
retrieves data on all markets for upbit
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict[]: an array of objects representing market data
|
||
"""
|
||
response = self.publicGetMarketAll(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "market": "KRW-BTC",
|
||
# "korean_name": "비트코인",
|
||
# "english_name": "Bitcoin"
|
||
# },
|
||
# ...,
|
||
# ]
|
||
#
|
||
return self.parse_markets(response)
|
||
|
||
def parse_market(self, market: dict) -> Market:
|
||
id = self.safe_string(market, 'market')
|
||
quoteId, baseId = id.split('-')
|
||
base = self.safe_currency_code(baseId)
|
||
quote = self.safe_currency_code(quoteId)
|
||
return self.safe_market_structure({
|
||
'id': id,
|
||
'symbol': base + '/' + quote,
|
||
'base': base,
|
||
'quote': quote,
|
||
'settle': None,
|
||
'baseId': baseId,
|
||
'quoteId': quoteId,
|
||
'settleId': None,
|
||
'type': 'spot',
|
||
'spot': True,
|
||
'margin': False,
|
||
'swap': False,
|
||
'future': False,
|
||
'option': False,
|
||
'active': True,
|
||
'contract': False,
|
||
'linear': None,
|
||
'inverse': None,
|
||
'taker': self.safe_number(self.options['tradingFeesByQuoteCurrency'], quote, self.fees['trading']['taker']),
|
||
'maker': self.safe_number(self.options['tradingFeesByQuoteCurrency'], quote, self.fees['trading']['maker']),
|
||
'contractSize': None,
|
||
'expiry': None,
|
||
'expiryDatetime': None,
|
||
'strike': None,
|
||
'optionType': None,
|
||
'precision': {
|
||
'price': self.parse_number('1e-8'),
|
||
'amount': self.parse_number('1e-8'),
|
||
},
|
||
'limits': {
|
||
'leverage': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'amount': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'price': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
'cost': {
|
||
'min': None,
|
||
'max': None,
|
||
},
|
||
},
|
||
'created': None,
|
||
'info': market,
|
||
})
|
||
|
||
def parse_balance(self, response) -> Balances:
|
||
result: dict = {
|
||
'info': response,
|
||
'timestamp': None,
|
||
'datetime': None,
|
||
}
|
||
for i in range(0, len(response)):
|
||
balance = response[i]
|
||
currencyId = self.safe_string(balance, 'currency')
|
||
code = self.safe_currency_code(currencyId)
|
||
account = self.account()
|
||
account['free'] = self.safe_string(balance, 'balance')
|
||
account['used'] = self.safe_string(balance, 'locked')
|
||
result[code] = account
|
||
return self.safe_balance(result)
|
||
|
||
def fetch_balance(self, params={}) -> Balances:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/전체-계좌-조회
|
||
https://global-docs.upbit.com/reference/overall-account-inquiry
|
||
|
||
query for balance and get the amount of funds available for trading or funds locked in orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
||
"""
|
||
self.load_markets()
|
||
response = self.privateGetAccounts(params)
|
||
#
|
||
# [{ currency: "BTC",
|
||
# "balance": "0.005",
|
||
# "locked": "0.0",
|
||
# "avg_krw_buy_price": "7446000",
|
||
# "modified": False },
|
||
# { currency: "ETH",
|
||
# "balance": "0.1",
|
||
# "locked": "0.0",
|
||
# "avg_krw_buy_price": "250000",
|
||
# "modified": False } ]
|
||
#
|
||
return self.parse_balance(response)
|
||
|
||
def fetch_order_books(self, symbols: Strings = None, limit: Int = None, params={}) -> OrderBooks:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/호가-정보-조회
|
||
https://global-docs.upbit.com/reference/order-book-list
|
||
|
||
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data for multiple markets
|
||
:param str[]|None symbols: list of unified market symbols, all symbols fetched if None, default is None
|
||
: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 symbol
|
||
"""
|
||
self.load_markets()
|
||
ids = None
|
||
if symbols is None:
|
||
ids = ','.join(self.ids)
|
||
else:
|
||
ids = self.market_ids(symbols)
|
||
ids = ','.join(ids)
|
||
request: dict = {
|
||
'markets': ids,
|
||
# 'count': limit,
|
||
}
|
||
if limit is not None:
|
||
request['count'] = limit
|
||
response = self.publicGetOrderbook(self.extend(request, params))
|
||
#
|
||
# [{ market: "BTC-ETH",
|
||
# "timestamp": 1542899030043,
|
||
# "total_ask_size": 109.57065201,
|
||
# "total_bid_size": 125.74430631,
|
||
# "orderbook_units": [{ask_price: 0.02926679,
|
||
# "bid_price": 0.02919904,
|
||
# "ask_size": 4.20293961,
|
||
# "bid_size": 11.65043576},
|
||
# ...,
|
||
# {ask_price: 0.02938209,
|
||
# "bid_price": 0.0291231,
|
||
# "ask_size": 0.05135782,
|
||
# "bid_size": 13.5595 } ]},
|
||
# { market: "KRW-BTC",
|
||
# "timestamp": 1542899034662,
|
||
# "total_ask_size": 12.89790974,
|
||
# "total_bid_size": 4.88395783,
|
||
# "orderbook_units": [{ask_price: 5164000,
|
||
# "bid_price": 5162000,
|
||
# "ask_size": 2.57606495,
|
||
# "bid_size": 0.214 },
|
||
# ...,
|
||
# {ask_price: 5176000,
|
||
# "bid_price": 5152000,
|
||
# "ask_size": 2.752,
|
||
# "bid_size": 0.4650305} ]} ]
|
||
#
|
||
result: dict = {}
|
||
for i in range(0, len(response)):
|
||
orderbook = response[i]
|
||
marketId = self.safe_string(orderbook, 'market')
|
||
symbol = self.safe_symbol(marketId, None, '-')
|
||
timestamp = self.safe_integer(orderbook, 'timestamp')
|
||
result[symbol] = {
|
||
'symbol': symbol,
|
||
'bids': self.sort_by(self.parse_bids_asks(orderbook['orderbook_units'], 'bid_price', 'bid_size'), 0, True),
|
||
'asks': self.sort_by(self.parse_bids_asks(orderbook['orderbook_units'], 'ask_price', 'ask_size'), 0),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'nonce': None,
|
||
}
|
||
return result
|
||
|
||
def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/호가-정보-조회
|
||
https://global-docs.upbit.com/reference/order-book-list
|
||
|
||
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
||
: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
|
||
"""
|
||
orderbooks = self.fetch_order_books([symbol], limit, params)
|
||
return self.safe_value(orderbooks, symbol)
|
||
|
||
def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
|
||
#
|
||
# { market: "BTC-ETH",
|
||
# "trade_date": "20181122",
|
||
# "trade_time": "104543",
|
||
# "trade_date_kst": "20181122",
|
||
# "trade_time_kst": "194543",
|
||
# "trade_timestamp": 1542883543096,
|
||
# "opening_price": 0.02976455,
|
||
# "high_price": 0.02992577,
|
||
# "low_price": 0.02934283,
|
||
# "trade_price": 0.02947773,
|
||
# "prev_closing_price": 0.02966,
|
||
# "change": "FALL",
|
||
# "change_price": 0.00018227,
|
||
# "change_rate": 0.0061453136,
|
||
# "signed_change_price": -0.00018227,
|
||
# "signed_change_rate": -0.0061453136,
|
||
# "trade_volume": 1.00000005,
|
||
# "acc_trade_price": 100.95825586,
|
||
# "acc_trade_price_24h": 289.58650166,
|
||
# "acc_trade_volume": 3409.85311036,
|
||
# "acc_trade_volume_24h": 9754.40510513,
|
||
# "highest_52_week_price": 0.12345678,
|
||
# "highest_52_week_date": "2018-02-01",
|
||
# "lowest_52_week_price": 0.023936,
|
||
# "lowest_52_week_date": "2017-12-08",
|
||
# "timestamp": 1542883543813 }
|
||
#
|
||
timestamp = self.safe_integer(ticker, 'trade_timestamp')
|
||
marketId = self.safe_string_2(ticker, 'market', 'code')
|
||
market = self.safe_market(marketId, market, '-')
|
||
last = self.safe_string(ticker, 'trade_price')
|
||
return self.safe_ticker({
|
||
'symbol': market['symbol'],
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'high': self.safe_string(ticker, 'high_price'),
|
||
'low': self.safe_string(ticker, 'low_price'),
|
||
'bid': None,
|
||
'bidVolume': None,
|
||
'ask': None,
|
||
'askVolume': None,
|
||
'vwap': None,
|
||
'open': self.safe_string(ticker, 'opening_price'),
|
||
'close': last,
|
||
'last': last,
|
||
'previousClose': self.safe_string(ticker, 'prev_closing_price'),
|
||
'change': self.safe_string(ticker, 'signed_change_price'),
|
||
'percentage': self.safe_string(ticker, 'signed_change_rate'),
|
||
'average': None,
|
||
'baseVolume': self.safe_string(ticker, 'acc_trade_volume_24h'),
|
||
'quoteVolume': self.safe_string(ticker, 'acc_trade_price_24h'),
|
||
'info': ticker,
|
||
}, market)
|
||
|
||
def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/ticker현재가-정보
|
||
https://global-docs.upbit.com/reference/tickers
|
||
|
||
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
||
:param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
||
"""
|
||
self.load_markets()
|
||
symbols = self.market_symbols(symbols)
|
||
ids = None
|
||
if symbols is None:
|
||
ids = ','.join(self.ids)
|
||
else:
|
||
ids = self.market_ids(symbols)
|
||
ids = ','.join(ids)
|
||
request: dict = {
|
||
'markets': ids,
|
||
}
|
||
response = self.publicGetTicker(self.extend(request, params))
|
||
#
|
||
# [{ market: "BTC-ETH",
|
||
# "trade_date": "20181122",
|
||
# "trade_time": "104543",
|
||
# "trade_date_kst": "20181122",
|
||
# "trade_time_kst": "194543",
|
||
# "trade_timestamp": 1542883543097,
|
||
# "opening_price": 0.02976455,
|
||
# "high_price": 0.02992577,
|
||
# "low_price": 0.02934283,
|
||
# "trade_price": 0.02947773,
|
||
# "prev_closing_price": 0.02966,
|
||
# "change": "FALL",
|
||
# "change_price": 0.00018227,
|
||
# "change_rate": 0.0061453136,
|
||
# "signed_change_price": -0.00018227,
|
||
# "signed_change_rate": -0.0061453136,
|
||
# "trade_volume": 1.00000005,
|
||
# "acc_trade_price": 100.95825586,
|
||
# "acc_trade_price_24h": 289.58650166,
|
||
# "acc_trade_volume": 3409.85311036,
|
||
# "acc_trade_volume_24h": 9754.40510513,
|
||
# "highest_52_week_price": 0.12345678,
|
||
# "highest_52_week_date": "2018-02-01",
|
||
# "lowest_52_week_price": 0.023936,
|
||
# "lowest_52_week_date": "2017-12-08",
|
||
# "timestamp": 1542883543813 }]
|
||
#
|
||
result: dict = {}
|
||
for t in range(0, len(response)):
|
||
ticker = self.parse_ticker(response[t])
|
||
symbol = ticker['symbol']
|
||
result[symbol] = ticker
|
||
return self.filter_by_array_tickers(result, 'symbol', symbols)
|
||
|
||
def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/ticker현재가-정보
|
||
https://global-docs.upbit.com/reference/tickers
|
||
|
||
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
||
: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>`
|
||
"""
|
||
tickers = self.fetch_tickers([symbol], params)
|
||
return self.safe_value(tickers, symbol)
|
||
|
||
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
||
#
|
||
# fetchTrades
|
||
#
|
||
# { market: "BTC-ETH",
|
||
# "trade_date_utc": "2018-11-22",
|
||
# "trade_time_utc": "13:55:24",
|
||
# "timestamp": 1542894924397,
|
||
# "trade_price": 0.02914289,
|
||
# "trade_volume": 0.20074397,
|
||
# "prev_closing_price": 0.02966,
|
||
# "change_price": -0.00051711,
|
||
# "ask_bid": "ASK",
|
||
# "sequential_id": 15428949259430000}
|
||
#
|
||
# fetchOrder trades
|
||
#
|
||
# {
|
||
# "market": "KRW-BTC",
|
||
# "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3",
|
||
# "price": "101000.0",
|
||
# "volume": "0.77368323",
|
||
# "funds": "78142.00623",
|
||
# "ask_fee": "117.213009345",
|
||
# "bid_fee": "117.213009345",
|
||
# "created_at": "2018-04-05T14:09:15+09:00",
|
||
# "side": "bid",
|
||
# }
|
||
#
|
||
id = self.safe_string_2(trade, 'sequential_id', 'uuid')
|
||
orderId = None
|
||
timestamp = self.safe_integer(trade, 'timestamp')
|
||
if timestamp is None:
|
||
timestamp = self.parse8601(self.safe_string(trade, 'created_at'))
|
||
side = None
|
||
askOrBid = self.safe_string_lower_2(trade, 'ask_bid', 'side')
|
||
if askOrBid == 'ask':
|
||
side = 'sell'
|
||
elif askOrBid == 'bid':
|
||
side = 'buy'
|
||
cost = self.safe_string(trade, 'funds')
|
||
price = self.safe_string_2(trade, 'trade_price', 'price')
|
||
amount = self.safe_string_2(trade, 'trade_volume', 'volume')
|
||
marketId = self.safe_string_2(trade, 'market', 'code')
|
||
market = self.safe_market(marketId, market, '-')
|
||
fee = None
|
||
feeCost = self.safe_string(trade, askOrBid + '_fee')
|
||
if feeCost is not None:
|
||
fee = {
|
||
'currency': market['quote'],
|
||
'cost': feeCost,
|
||
}
|
||
return self.safe_trade({
|
||
'id': id,
|
||
'info': trade,
|
||
'order': orderId,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'symbol': market['symbol'],
|
||
'type': None,
|
||
'side': side,
|
||
'takerOrMaker': None,
|
||
'price': price,
|
||
'amount': amount,
|
||
'cost': cost,
|
||
'fee': fee,
|
||
}, market)
|
||
|
||
def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/최근-체결-내역
|
||
https://global-docs.upbit.com/reference/today-trades-history
|
||
|
||
get the list of most recent trades for a particular symbol
|
||
:param str symbol: unified symbol of the market to fetch trades for
|
||
:param int [since]: timestamp in ms of the earliest trade to fetch
|
||
:param int [limit]: the maximum amount of trades to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
if limit is None:
|
||
limit = 200
|
||
request: dict = {
|
||
'market': market['id'],
|
||
'count': limit,
|
||
}
|
||
response = self.publicGetTradesTicks(self.extend(request, params))
|
||
#
|
||
# [{ market: "BTC-ETH",
|
||
# "trade_date_utc": "2018-11-22",
|
||
# "trade_time_utc": "13:55:24",
|
||
# "timestamp": 1542894924397,
|
||
# "trade_price": 0.02914289,
|
||
# "trade_volume": 0.20074397,
|
||
# "prev_closing_price": 0.02966,
|
||
# "change_price": -0.00051711,
|
||
# "ask_bid": "ASK",
|
||
# "sequential_id": 15428949259430000},
|
||
# { market: "BTC-ETH",
|
||
# "trade_date_utc": "2018-11-22",
|
||
# "trade_time_utc": "13:03:10",
|
||
# "timestamp": 1542891790123,
|
||
# "trade_price": 0.02917,
|
||
# "trade_volume": 7.392,
|
||
# "prev_closing_price": 0.02966,
|
||
# "change_price": -0.00049,
|
||
# "ask_bid": "ASK",
|
||
# "sequential_id": 15428917910540000} ]
|
||
#
|
||
return self.parse_trades(response, market, since, limit)
|
||
|
||
def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/주문-가능-정보
|
||
https://global-docs.upbit.com/reference/available-order-information
|
||
|
||
fetch the trading fees for a market
|
||
:param str symbol: unified market symbol
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
request: dict = {
|
||
'market': market['id'],
|
||
}
|
||
response = self.privateGetOrdersChance(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "bid_fee": "0.0005",
|
||
# "ask_fee": "0.0005",
|
||
# "maker_bid_fee": "0.0005",
|
||
# "maker_ask_fee": "0.0005",
|
||
# "market": {
|
||
# "id": "KRW-BTC",
|
||
# "name": "BTC/KRW",
|
||
# "order_types": ["limit"],
|
||
# "order_sides": ["ask", "bid"],
|
||
# "bid": {"currency": "KRW", "price_unit": null, "min_total": 5000},
|
||
# "ask": {"currency": "BTC", "price_unit": null, "min_total": 5000},
|
||
# "max_total": "1000000000.0",
|
||
# "state": "active"
|
||
# },
|
||
# "bid_account": {
|
||
# "currency": "KRW",
|
||
# "balance": "0.34202415",
|
||
# "locked": "4999.99999922",
|
||
# "avg_buy_price": "0",
|
||
# "avg_buy_price_modified": True,
|
||
# "unit_currency": "KRW"
|
||
# },
|
||
# "ask_account": {
|
||
# "currency": "BTC",
|
||
# "balance": "0.00048",
|
||
# "locked": "0.0",
|
||
# "avg_buy_price": "20870000",
|
||
# "avg_buy_price_modified": False,
|
||
# "unit_currency": "KRW"
|
||
# }
|
||
# }
|
||
#
|
||
askFee = self.safe_string(response, 'ask_fee')
|
||
bidFee = self.safe_string(response, 'bid_fee')
|
||
taker = Precise.string_max(askFee, bidFee)
|
||
makerAskFee = self.safe_string(response, 'maker_ask_fee')
|
||
makerBidFee = self.safe_string(response, 'maker_bid_fee')
|
||
maker = Precise.string_max(makerAskFee, makerBidFee)
|
||
return {
|
||
'info': response,
|
||
'symbol': symbol,
|
||
'maker': self.parse_number(maker),
|
||
'taker': self.parse_number(taker),
|
||
'percentage': True,
|
||
'tierBased': False,
|
||
}
|
||
|
||
def fetch_trading_fees(self, params={}) -> TradingFees:
|
||
"""
|
||
fetch the trading fees for markets
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a `trading fee structure <https://docs.ccxt.com/#/?id=trading-fee-structure>`
|
||
"""
|
||
self.load_markets()
|
||
fetchMarketResponse = self.fetch_markets(params)
|
||
response: dict = {}
|
||
for i in range(0, len(fetchMarketResponse)):
|
||
element: dict = {}
|
||
element['maker'] = self.safe_number(fetchMarketResponse[i], 'maker')
|
||
element['taker'] = self.safe_number(fetchMarketResponse[i], 'taker')
|
||
element['symbol'] = self.safe_string(fetchMarketResponse[i], 'symbol')
|
||
element['percentage'] = True
|
||
element['tierBased'] = False
|
||
element['info'] = fetchMarketResponse[i]
|
||
response[self.safe_string(fetchMarketResponse[i], 'symbol')] = element
|
||
return response
|
||
|
||
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
||
#
|
||
# {
|
||
# "market": "BTC-ETH",
|
||
# "candle_date_time_utc": "2018-11-22T13:47:00",
|
||
# "candle_date_time_kst": "2018-11-22T22:47:00",
|
||
# "opening_price": 0.02915963,
|
||
# "high_price": 0.02915963,
|
||
# "low_price": 0.02915448,
|
||
# "trade_price": 0.02915448,
|
||
# "timestamp": 1542894473674,
|
||
# "candle_acc_trade_price": 0.0981629437535248,
|
||
# "candle_acc_trade_volume": 3.36693173,
|
||
# "unit": 1
|
||
# }
|
||
#
|
||
return [
|
||
self.parse8601(self.safe_string(ohlcv, 'candle_date_time_utc')),
|
||
self.safe_number(ohlcv, 'opening_price'),
|
||
self.safe_number(ohlcv, 'high_price'),
|
||
self.safe_number(ohlcv, 'low_price'),
|
||
self.safe_number(ohlcv, 'trade_price'),
|
||
self.safe_number(ohlcv, 'candle_acc_trade_volume'), # base volume
|
||
]
|
||
|
||
def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/분minute-캔들-1
|
||
https://global-docs.upbit.com/reference/minutes
|
||
|
||
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
||
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
||
:param str timeframe: the length of time each candle represents
|
||
:param int [since]: timestamp in ms of the earliest candle to fetch
|
||
:param int [limit]: the maximum amount of candles to fetch
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
timeframePeriod = self.parse_timeframe(timeframe)
|
||
timeframeValue = self.safe_string(self.timeframes, timeframe, timeframe)
|
||
if limit is None:
|
||
limit = 200
|
||
request: dict = {
|
||
'market': market['id'],
|
||
'timeframe': timeframeValue,
|
||
'count': limit,
|
||
}
|
||
response = None
|
||
if since is not None:
|
||
# convert `since` to `to` value
|
||
request['to'] = self.iso8601(self.sum(since, timeframePeriod * limit * 1000))
|
||
if timeframeValue == 'minutes':
|
||
numMinutes = int(round(timeframePeriod / 60))
|
||
request['unit'] = numMinutes
|
||
response = self.publicGetCandlesTimeframeUnit(self.extend(request, params))
|
||
else:
|
||
response = self.publicGetCandlesTimeframe(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "market": "BTC-ETH",
|
||
# "candle_date_time_utc": "2018-11-22T13:47:00",
|
||
# "candle_date_time_kst": "2018-11-22T22:47:00",
|
||
# "opening_price": 0.02915963,
|
||
# "high_price": 0.02915963,
|
||
# "low_price": 0.02915448,
|
||
# "trade_price": 0.02915448,
|
||
# "timestamp": 1542894473674,
|
||
# "candle_acc_trade_price": 0.0981629437535248,
|
||
# "candle_acc_trade_volume": 3.36693173,
|
||
# "unit": 1
|
||
# },
|
||
# {
|
||
# "market": "BTC-ETH",
|
||
# "candle_date_time_utc": "2018-11-22T10:06:00",
|
||
# "candle_date_time_kst": "2018-11-22T19:06:00",
|
||
# "opening_price": 0.0294,
|
||
# "high_price": 0.02940882,
|
||
# "low_price": 0.02934283,
|
||
# "trade_price": 0.02937354,
|
||
# "timestamp": 1542881219276,
|
||
# "candle_acc_trade_price": 0.0762597110943884,
|
||
# "candle_acc_trade_volume": 2.5949617,
|
||
# "unit": 1
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_ohlcvs(response, market, timeframe, since, limit)
|
||
|
||
def calc_order_price(self, symbol: str, amount: float, price: Num = None, params={}) -> str:
|
||
quoteAmount = None
|
||
createMarketBuyOrderRequiresPrice = self.safe_value(self.options, 'createMarketBuyOrderRequiresPrice')
|
||
cost = self.safe_string(params, 'cost')
|
||
if cost is not None:
|
||
quoteAmount = self.cost_to_precision(symbol, cost)
|
||
elif createMarketBuyOrderRequiresPrice:
|
||
if price is None or amount is None:
|
||
raise InvalidOrder(self.id + ' createOrder() requires the price and amount argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
|
||
amountString = self.number_to_string(amount)
|
||
priceString = self.number_to_string(price)
|
||
costRequest = Precise.string_mul(amountString, priceString)
|
||
quoteAmount = self.cost_to_precision(symbol, costRequest)
|
||
else:
|
||
if amount is None:
|
||
raise ArgumentsRequired(self.id + ' When createMarketBuyOrderRequiresPrice is False, "amount" is required and should be the total quote amount to spend.')
|
||
quoteAmount = self.cost_to_precision(symbol, amount)
|
||
return quoteAmount
|
||
|
||
def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
||
"""
|
||
create a trade order
|
||
|
||
https://docs.upbit.com/kr/reference/주문하기
|
||
https://global-docs.upbit.com/reference/order
|
||
|
||
:param str symbol: unified symbol of the market to create an order in
|
||
:param str type: supports 'market' and 'limit'. if params.ordType is set to best, a best-type order will be created regardless of the value of type.
|
||
:param str side: 'buy' or 'sell'
|
||
:param float amount: how much you want to trade in units of the base currency
|
||
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param float [params.cost]: for market buy and best buy orders, the quote quantity that can be used alternative for the amount
|
||
:param str [params.ordType]: self field can be used to place a ‘best’ type order
|
||
:param str [params.timeInForce]: 'IOC' or 'FOK' for limit or best type orders, 'PO' for limit orders. self field is required when the order type is 'best'.
|
||
:param str [params.selfTradePrevention]: 'reduce', 'cancel_maker', 'cancel_taker' {@link https://global-docs.upbit.com/docs/smp}
|
||
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
market = self.market(symbol)
|
||
clientOrderId = self.safe_string(params, 'clientOrderId')
|
||
customType = self.safe_string_2(params, 'ordType', 'ord_type')
|
||
postOnly = self.is_post_only(type == 'market', False, params)
|
||
timeInForce = self.safe_string_lower_2(params, 'timeInForce', 'time_in_force')
|
||
selfTradePrevention = self.safe_string_2(params, 'selfTradePrevention', 'smp_type')
|
||
if postOnly and (selfTradePrevention is not None):
|
||
raise ExchangeError(self.id + ' createOrder() does not support post_only and selfTradePrevention simultaneously.')
|
||
orderSide = None
|
||
if side == 'buy':
|
||
orderSide = 'bid'
|
||
elif side == 'sell':
|
||
orderSide = 'ask'
|
||
else:
|
||
raise InvalidOrder(self.id + ' createOrder() supports only buy or sell in the side argument.')
|
||
request: dict = {
|
||
'market': market['id'],
|
||
'side': orderSide,
|
||
# 'smp_type': selfTradePrevention,
|
||
}
|
||
if type == 'limit':
|
||
if price is None or amount is None:
|
||
raise ArgumentsRequired(self.id + ' the limit type order in createOrder() is required price and amount.')
|
||
request['ord_type'] = 'limit'
|
||
request['price'] = self.price_to_precision(symbol, price)
|
||
request['volume'] = self.amount_to_precision(symbol, amount)
|
||
elif type == 'market':
|
||
if side == 'buy':
|
||
request['ord_type'] = 'price'
|
||
orderPrice = self.calc_order_price(symbol, amount, price, params)
|
||
request['price'] = orderPrice
|
||
else:
|
||
if amount is None:
|
||
raise ArgumentsRequired(self.id + ' the market sell type order in createOrder() is required amount.')
|
||
request['ord_type'] = 'market'
|
||
request['volume'] = self.amount_to_precision(symbol, amount)
|
||
else:
|
||
raise InvalidOrder(self.id + ' createOrder() supports only limit or market types in the type argument.')
|
||
if customType == 'best':
|
||
params = self.omit(params, ['ordType', 'ord_type'])
|
||
request['ord_type'] = 'best'
|
||
if side == 'buy':
|
||
orderPrice = self.calc_order_price(symbol, amount, price, params)
|
||
request['price'] = orderPrice
|
||
else:
|
||
if amount is None:
|
||
raise ArgumentsRequired(self.id + ' the best sell type order in createOrder() is required amount.')
|
||
request['volume'] = self.amount_to_precision(symbol, amount)
|
||
if clientOrderId is not None:
|
||
request['identifier'] = clientOrderId
|
||
if postOnly:
|
||
if request['ord_type'] != 'limit':
|
||
raise InvalidOrder(self.id + ' postOnly orders are only supported for limit orders')
|
||
request['time_in_force'] = 'post_only'
|
||
if timeInForce is not None:
|
||
if timeInForce == 'ioc' or timeInForce == 'fok':
|
||
request['time_in_force'] = timeInForce
|
||
if request['ord_type'] == 'best' and timeInForce is None:
|
||
raise ArgumentsRequired(self.id + ' createOrder() requires a timeInForce parameter for best type orders')
|
||
params = self.omit(params, ['timeInForce', 'time_in_force', 'postOnly', 'clientOrderId', 'cost', 'selfTradePrevention', 'smp_type'])
|
||
response = self.privatePostOrders(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "uuid": "cdd92199-2897-4e14-9448-f923320408ad",
|
||
# "side": "bid",
|
||
# "ord_type": "limit",
|
||
# "price": "100.0",
|
||
# "avg_price": "0.0",
|
||
# "state": "wait",
|
||
# "market": "KRW-BTC",
|
||
# "created_at": "2018-04-10T15:42:23+09:00",
|
||
# "volume": "0.01",
|
||
# "remaining_volume": "0.01",
|
||
# "reserved_fee": "0.0015",
|
||
# "remaining_fee": "0.0015",
|
||
# "paid_fee": "0.0",
|
||
# "locked": "1.0015",
|
||
# "executed_volume": "0.0",
|
||
# "trades_count": 0
|
||
# }
|
||
#
|
||
return self.parse_order(response)
|
||
|
||
def cancel_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/주문-취소
|
||
https://global-docs.upbit.com/reference/order-cancel
|
||
|
||
cancels an open order
|
||
:param str id: order id
|
||
:param str symbol: not used by upbit cancelOrder()
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'uuid': id,
|
||
}
|
||
response = self.privateDeleteOrder(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "uuid": "cdd92199-2897-4e14-9448-f923320408ad",
|
||
# "side": "bid",
|
||
# "ord_type": "limit",
|
||
# "price": "100.0",
|
||
# "state": "wait",
|
||
# "market": "KRW-BTC",
|
||
# "created_at": "2018-04-10T15:42:23+09:00",
|
||
# "volume": "0.01",
|
||
# "remaining_volume": "0.01",
|
||
# "reserved_fee": "0.0015",
|
||
# "remaining_fee": "0.0015",
|
||
# "paid_fee": "0.0",
|
||
# "locked": "1.0015",
|
||
# "executed_volume": "0.0",
|
||
# "trades_count": 0
|
||
# }
|
||
#
|
||
return self.parse_order(response)
|
||
|
||
def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}) -> Order:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/취소-후-재주문
|
||
https://global-docs.upbit.com/reference/cancel-and-new
|
||
|
||
canceled existing order and create new order. It's only generated same side and symbol canceled order. it returns the data of the canceled order, except for `new_order_uuid` and `new_identifier`. to get the details of the new order, use `fetchOrder(new_order_uuid)`.
|
||
:param str id: the uuid of the previous order you want to edit.
|
||
:param str symbol: the symbol of the new order. it must be the same symbol of the previous order.
|
||
:param str type: the type of the new order. only limit or market is accepted. if params.newOrdType is set to best, a best-type order will be created regardless of the value of type.
|
||
:param str side: the side of the new order. it must be the same side of the previous order.
|
||
:param number amount: the amount of the asset you want to buy or sell. It could be overridden by specifying the new_volume parameter in params.
|
||
:param number price: the price of the asset you want to buy or sell. It could be overridden by specifying the new_price parameter in params.
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint.
|
||
:param str [params.clientOrderId]: to identify the previous order, either the id or self field is hasattr(self, required) method.
|
||
:param float [params.cost]: for market buy and best buy orders, the quote quantity that can be used alternative for the amount.
|
||
:param str [params.newTimeInForce]: 'IOC' or 'FOK' for limit or best type orders, 'PO' for limit orders. self field is required when the order type is 'best'.
|
||
:param str [params.newClientOrderId]: the order ID that the user can define.
|
||
:param str [params.newOrdType]: self field only accepts limit, price, market, or best. You can refer to the Upbit developer documentation for details on how to use self field.
|
||
:param str [params.selfTradePrevention]: 'reduce', 'cancel_maker', 'cancel_taker' {@link https://global-docs.upbit.com/docs/smp}
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
prevClientOrderId = self.safe_string(params, 'clientOrderId')
|
||
customType = self.safe_string_2(params, 'newOrdType', 'new_ord_type')
|
||
clientOrderId = self.safe_string(params, 'newClientOrderId')
|
||
postOnly = self.is_post_only(type == 'market', False, params)
|
||
timeInForce = self.safe_string_lower_2(params, 'newTimeInForce', 'new_time_in_force')
|
||
selfTradePrevention = self.safe_string_2(params, 'selfTradePrevention', 'new_smp_type')
|
||
if postOnly and (selfTradePrevention is not None):
|
||
raise ExchangeError(self.id + ' editOrder() does not support post_only and selfTradePrevention simultaneously.')
|
||
params = self.omit(params, 'clientOrderId')
|
||
if id is not None:
|
||
request['prev_order_uuid'] = id
|
||
elif prevClientOrderId is not None:
|
||
request['prev_order_identifier'] = prevClientOrderId
|
||
else:
|
||
raise ArgumentsRequired(self.id + ' editOrder() is required id or clientOrderId.')
|
||
if type == 'limit':
|
||
if price is None or amount is None:
|
||
raise ArgumentsRequired(self.id + ' editOrder() is required price and amount to create limit type order.')
|
||
request['new_ord_type'] = 'limit'
|
||
request['new_price'] = self.price_to_precision(symbol, price)
|
||
request['new_volume'] = self.amount_to_precision(symbol, amount)
|
||
elif type == 'market':
|
||
if side == 'buy':
|
||
request['new_ord_type'] = 'price'
|
||
orderPrice = self.calc_order_price(symbol, amount, price, params)
|
||
request['new_price'] = orderPrice
|
||
else:
|
||
if amount is None:
|
||
raise ArgumentsRequired(self.id + ' editOrder() is required amount to create market sell type order.')
|
||
request['new_ord_type'] = 'market'
|
||
request['new_volume'] = self.amount_to_precision(symbol, amount)
|
||
else:
|
||
raise InvalidOrder(self.id + ' editOrder() supports only limit or market types in the type argument.')
|
||
if customType == 'best':
|
||
params = self.omit(params, ['newOrdType', 'new_ord_type'])
|
||
request['new_ord_type'] = 'best'
|
||
if side == 'buy':
|
||
orderPrice = self.calc_order_price(symbol, amount, price, params)
|
||
request['new_price'] = orderPrice
|
||
else:
|
||
if amount is None:
|
||
raise ArgumentsRequired(self.id + ' editOrder() is required amount to create best sell order.')
|
||
request['new_volume'] = self.amount_to_precision(symbol, amount)
|
||
if clientOrderId is not None:
|
||
request['new_identifier'] = clientOrderId
|
||
if selfTradePrevention is not None:
|
||
request['new_smp_type'] = selfTradePrevention
|
||
if postOnly:
|
||
if request['new_ord_type'] != 'limit':
|
||
raise InvalidOrder(self.id + ' postOnly orders are only supported for limit orders')
|
||
request['new_time_in_force'] = 'post_only'
|
||
if timeInForce is not None:
|
||
if timeInForce == 'ioc' or timeInForce == 'fok':
|
||
request['new_time_in_force'] = timeInForce
|
||
if request['new_ord_type'] == 'best' and timeInForce is None:
|
||
raise ArgumentsRequired(self.id + ' editOrder() requires a timeInForce parameter for best type orders')
|
||
params = self.omit(params, ['newTimeInForce', 'new_time_in_force', 'postOnly', 'newClientOrderId', 'cost', 'selfTradePrevention', 'new_smp_type'])
|
||
# print('check the each request params: ', request)
|
||
response = self.privatePostOrdersCancelAndNew(self.extend(request, params))
|
||
# {
|
||
# uuid: '63b38774-27db-4439-ac20-1be16a24d18e', #previous order data
|
||
# side: 'bid', #previous order data
|
||
# ord_type: 'limit', #previous order data
|
||
# price: '100000000', #previous order data
|
||
# state: 'wait', #previous order data
|
||
# market: 'KRW-BTC', #previous order data
|
||
# created_at: '2025-04-01T15:30:47+09:00', #previous order data
|
||
# volume: '0.00008', #previous order data
|
||
# remaining_volume: '0.00008', #previous order data
|
||
# reserved_fee: '4', #previous order data
|
||
# remaining_fee: '4', #previous order data
|
||
# paid_fee: '0', #previous order data
|
||
# locked: '8004', #previous order data
|
||
# executed_volume: '0', #previous order data
|
||
# trades_count: '0', #previous order data
|
||
# identifier: '21', #previous order data
|
||
# new_order_uuid: 'cb1cce56-6237-4a78-bc11-4cfffc1bb4c2', # new order data
|
||
# new_order_identifier: '22' # new order data
|
||
# }
|
||
result: dict = {}
|
||
result['uuid'] = self.safe_string(response, 'new_order_uuid')
|
||
result['identifier'] = self.safe_string(response, 'new_order_identifier')
|
||
result['side'] = self.safe_string(response, 'side')
|
||
result['market'] = self.safe_string(response, 'market')
|
||
return self.parse_order(result)
|
||
|
||
def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/입금-리스트-조회
|
||
https://global-docs.upbit.com/reference/deposit-list-inquiry
|
||
|
||
fetch all deposits made to an account
|
||
: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>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
# 'page': 1,
|
||
# 'order_by': 'asc', # 'desc'
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['currency'] = currency['id']
|
||
if limit is not None:
|
||
request['limit'] = limit # default is 100
|
||
response = self.privateGetDeposits(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "type": "deposit",
|
||
# "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830",
|
||
# "currency": "KRW",
|
||
# "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a",
|
||
# "state": "ACCEPTED",
|
||
# "created_at": "2017-12-08T15:38:02+09:00",
|
||
# "done_at": "2017-12-08T15:38:02+09:00",
|
||
# "amount": "100000.0",
|
||
# "fee": "0.0"
|
||
# },
|
||
# ...,
|
||
# ]
|
||
#
|
||
return self.parse_transactions(response, currency, since, limit)
|
||
|
||
def fetch_deposit(self, id: str, code: Str = None, params={}):
|
||
"""
|
||
fetch information on a deposit
|
||
|
||
https://docs.upbit.com/kr/reference/개별-입금-조회
|
||
https://global-docs.upbit.com/reference/individual-deposit-inquiry
|
||
|
||
:param str id: the unique id for the deposit
|
||
:param str [code]: unified currency code of the currency deposited
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.txid]: withdrawal transaction id, the id argument is reserved for uuid
|
||
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'uuid': id,
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['currency'] = currency['id']
|
||
response = self.privateGetDeposit(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "type": "deposit",
|
||
# "uuid": "7f54527e-2eee-4268-860e-fd8b9d7fe3c7",
|
||
# "currency": "ADA",
|
||
# "net_type": "ADA",
|
||
# "txid": "99795bbfeca91eaa071068bb659b33eeb65d8aaff2551fdf7c78f345d188952b",
|
||
# "state": "ACCEPTED",
|
||
# "created_at": "2023-12-12T04:58:41Z",
|
||
# "done_at": "2023-12-12T05:31:50Z",
|
||
# "amount": "35.72344",
|
||
# "fee": "0.0",
|
||
# "transaction_type": "default"
|
||
# }
|
||
#
|
||
return self.parse_transaction(response, currency)
|
||
|
||
def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/전체-출금-조회
|
||
https://global-docs.upbit.com/reference/withdrawal-list-inquiry
|
||
|
||
fetch all withdrawals made from an account
|
||
: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>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
# 'state': 'submitting', # 'submitted', 'almost_accepted', 'rejected', 'accepted', 'processing', 'done', 'canceled'
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['currency'] = currency['id']
|
||
if limit is not None:
|
||
request['limit'] = limit # default is 100
|
||
response = self.privateGetWithdraws(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "type": "withdraw",
|
||
# "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79",
|
||
# "currency": "BTC",
|
||
# "txid": null,
|
||
# "state": "processing",
|
||
# "created_at": "2018-04-13T11:24:01+09:00",
|
||
# "done_at": null,
|
||
# "amount": "0.01",
|
||
# "fee": "0.0",
|
||
# "krw_amount": "80420.0"
|
||
# },
|
||
# ...,
|
||
# ]
|
||
#
|
||
return self.parse_transactions(response, currency, since, limit)
|
||
|
||
def fetch_withdrawal(self, id: str, code: Str = None, params={}):
|
||
"""
|
||
fetch data on a currency withdrawal via the withdrawal id
|
||
|
||
https://docs.upbit.com/kr/reference/개별-출금-조회
|
||
https://global-docs.upbit.com/reference/individual-withdrawal-inquiry
|
||
|
||
:param str id: the unique id for the withdrawal
|
||
:param str [code]: unified currency code of the currency withdrawn
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str [params.txid]: withdrawal transaction id, the id argument is reserved for uuid
|
||
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'uuid': id,
|
||
}
|
||
currency = None
|
||
if code is not None:
|
||
currency = self.currency(code)
|
||
request['currency'] = currency['id']
|
||
response = self.privateGetWithdraw(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "type": "withdraw",
|
||
# "uuid": "95ef274b-23a6-4de4-95b0-5cbef4ca658f",
|
||
# "currency": "ADA",
|
||
# "net_type": "ADA",
|
||
# "txid": "b1528f149297a71671b86636f731f8fdb0ff53da0f1d8c19093d59df96f34583",
|
||
# "state": "DONE",
|
||
# "created_at": "2023-12-14T02:46:52Z",
|
||
# "done_at": "2023-12-14T03:10:11Z",
|
||
# "amount": "35.22344",
|
||
# "fee": "0.5",
|
||
# "transaction_type": "default"
|
||
# }
|
||
#
|
||
return self.parse_transaction(response, currency)
|
||
|
||
def parse_transaction_status(self, status: Str):
|
||
statuses: dict = {
|
||
'submitting': 'pending', # 처리 중
|
||
'submitted': 'pending', # 처리 완료
|
||
'almost_accepted': 'pending', # 출금대기중
|
||
'rejected': 'failed', # 거부
|
||
'accepted': 'ok', # 승인됨
|
||
'processing': 'pending', # 처리 중
|
||
'done': 'ok', # 완료
|
||
'canceled': 'canceled', # 취소됨
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
||
#
|
||
# fetchDeposits, fetchDeposit
|
||
#
|
||
# {
|
||
# "type": "deposit",
|
||
# "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830",
|
||
# "currency": "KRW",
|
||
# "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a",
|
||
# "state": "ACCEPTED",
|
||
# "created_at": "2017-12-08T15:38:02+09:00",
|
||
# "done_at": "2017-12-08T15:38:02+09:00",
|
||
# "amount": "100000.0",
|
||
# "fee": "0.0"
|
||
# }
|
||
#
|
||
# fetchWithdrawals, fetchWithdrawal
|
||
#
|
||
# {
|
||
# "type": "withdraw",
|
||
# "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79",
|
||
# "currency": "BTC",
|
||
# "txid": "cd81e9b45df8da29f936836e58c907a106057e454a45767a7b06fcb19b966bba",
|
||
# "state": "processing",
|
||
# "created_at": "2018-04-13T11:24:01+09:00",
|
||
# "done_at": null,
|
||
# "amount": "0.01",
|
||
# "fee": "0.0",
|
||
# "krw_amount": "80420.0"
|
||
# }
|
||
#
|
||
address = None # not present in the data structure received from the exchange
|
||
tag = None # not present in the data structure received from the exchange
|
||
updatedRaw = self.safe_string(transaction, 'done_at')
|
||
timestamp = self.parse8601(self.safe_string(transaction, 'created_at', updatedRaw))
|
||
type = self.safe_string(transaction, 'type')
|
||
if type == 'withdraw':
|
||
type = 'withdrawal'
|
||
currencyId = self.safe_string(transaction, 'currency')
|
||
code = self.safe_currency_code(currencyId, currency)
|
||
return {
|
||
'info': transaction,
|
||
'id': self.safe_string(transaction, 'uuid'),
|
||
'currency': code,
|
||
'amount': self.safe_number(transaction, 'amount'),
|
||
'network': None,
|
||
'address': address,
|
||
'addressTo': None,
|
||
'addressFrom': None,
|
||
'tag': tag,
|
||
'tagTo': None,
|
||
'tagFrom': None,
|
||
'status': self.parse_transaction_status(self.safe_string_lower(transaction, 'state')),
|
||
'type': type,
|
||
'updated': self.parse8601(updatedRaw),
|
||
'txid': self.safe_string(transaction, 'txid'),
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'internal': None,
|
||
'comment': None,
|
||
'fee': {
|
||
'currency': code,
|
||
'cost': self.safe_number(transaction, 'fee'),
|
||
},
|
||
}
|
||
|
||
def parse_order_status(self, status: Str):
|
||
statuses: dict = {
|
||
'wait': 'open',
|
||
'done': 'closed',
|
||
'cancel': 'canceled',
|
||
}
|
||
return self.safe_string(statuses, status, status)
|
||
|
||
def parse_order(self, order: dict, market: Market = None) -> Order:
|
||
# {
|
||
# "market": "KRW-USDT",
|
||
# "uuid": "3b67e543-8ad3-48d0-8451-0dad315cae73",
|
||
# "side": "ask",
|
||
# "ord_type": "market",
|
||
# "state": "done",
|
||
# "created_at": "2025-08-09T16:44:00+09:00",
|
||
# "volume": "5.377594",
|
||
# "remaining_volume": "0",
|
||
# "executed_volume": "5.377594",
|
||
# "reserved_fee": "0",
|
||
# "remaining_fee": "0",
|
||
# "paid_fee": "3.697095875",
|
||
# "locked": "0",
|
||
# "prevented_volume": "0",
|
||
# "prevented_locked": "0",
|
||
# "trades_count": 1,
|
||
# "trades": [
|
||
# {
|
||
# "market": "KRW-USDT",
|
||
# "uuid": "795dff29-bba6-49b2-baab-63473ab7931c",
|
||
# "price": "1375",
|
||
# "volume": "5.377594",
|
||
# "funds": "7394.19175",
|
||
# "trend": "down",
|
||
# "created_at": "2025-08-09T16:44:00.597751+09:00",
|
||
# "side": "ask"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
# fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders
|
||
#
|
||
# {
|
||
# "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
|
||
# "side": "ask",
|
||
# "ord_type": "limit",
|
||
# "price": "1.5",
|
||
# "state": "wait",
|
||
# "market": "SGD-XRP",
|
||
# "created_at": "2024-06-05T09:37:10Z",
|
||
# "volume": "10",
|
||
# "remaining_volume": "10",
|
||
# "reserved_fee": "0",
|
||
# "remaining_fee": "0",
|
||
# "paid_fee": "0",
|
||
# "locked": "10",
|
||
# "executed_volume": "0",
|
||
# "executed_funds": "0",
|
||
# "trades_count": 0,
|
||
# "time_in_force": "ioc"
|
||
# }
|
||
#
|
||
# {
|
||
# uuid: '63b38774-27db-4439-ac20-1be16a24d18e',
|
||
# side: 'bid',
|
||
# ord_type: 'limit',
|
||
# price: '100000000',
|
||
# state: 'wait',
|
||
# market: 'KRW-BTC',
|
||
# created_at: '2025-04-01T15:30:47+09:00',
|
||
# volume: '0.00008',
|
||
# remaining_volume: '0.00008',
|
||
# reserved_fee: '4',
|
||
# remaining_fee: '4',
|
||
# paid_fee: '0',
|
||
# locked: '8004',
|
||
# executed_volume: '0',
|
||
# trades_count: '0',
|
||
# identifier: '21',
|
||
# new_order_uuid: 'cb1cce56-6237-4a78-bc11-4cfffc1bb4c2',
|
||
# new_order_identifier: '22'
|
||
# }
|
||
id = self.safe_string(order, 'uuid')
|
||
side = self.safe_string(order, 'side')
|
||
if side == 'bid':
|
||
side = 'buy'
|
||
else:
|
||
side = 'sell'
|
||
identifier = self.safe_string(order, 'identifier')
|
||
type = self.safe_string(order, 'ord_type')
|
||
timestamp = self.parse8601(self.safe_string(order, 'created_at'))
|
||
status = self.parse_order_status(self.safe_string(order, 'state'))
|
||
lastTradeTimestamp = None
|
||
price = self.safe_string(order, 'price')
|
||
amount = self.safe_string(order, 'volume')
|
||
remaining = self.safe_string(order, 'remaining_volume')
|
||
filled = self.safe_string(order, 'executed_volume')
|
||
cost = None
|
||
if type == 'price':
|
||
type = 'market'
|
||
cost = price
|
||
price = None
|
||
average = None
|
||
fee = None
|
||
feeCost = self.safe_string(order, 'paid_fee')
|
||
marketId = self.safe_string(order, 'market')
|
||
market = self.safe_market(marketId, market)
|
||
trades = self.safe_value(order, 'trades', [])
|
||
trades = self.parse_trades(trades, market, None, None, {
|
||
'order': id,
|
||
'type': type,
|
||
})
|
||
numTrades = len(trades)
|
||
if numTrades > 0:
|
||
# the timestamp in fetchOrder trades is missing
|
||
lastTradeTimestamp = trades[numTrades - 1]['timestamp']
|
||
getFeesFromTrades = False
|
||
if feeCost is None:
|
||
getFeesFromTrades = True
|
||
feeCost = '0'
|
||
cost = '0'
|
||
for i in range(0, numTrades):
|
||
trade = trades[i]
|
||
cost = Precise.string_add(cost, self.safe_string(trade, 'cost'))
|
||
if getFeesFromTrades:
|
||
tradeFee = self.safe_value(trades[i], 'fee', {})
|
||
tradeFeeCost = self.safe_string(tradeFee, 'cost')
|
||
if tradeFeeCost is not None:
|
||
feeCost = Precise.string_add(feeCost, tradeFeeCost)
|
||
average = Precise.string_div(cost, filled)
|
||
if feeCost is not None:
|
||
fee = {
|
||
'currency': market['quote'],
|
||
'cost': feeCost,
|
||
}
|
||
return self.safe_order({
|
||
'info': order,
|
||
'id': id,
|
||
'clientOrderId': identifier,
|
||
'timestamp': timestamp,
|
||
'datetime': self.iso8601(timestamp),
|
||
'lastTradeTimestamp': lastTradeTimestamp,
|
||
'symbol': market['symbol'],
|
||
'type': type,
|
||
'timeInForce': self.safe_string_upper(order, 'time_in_force'),
|
||
'postOnly': None,
|
||
'side': side,
|
||
'price': price,
|
||
'triggerPrice': None,
|
||
'cost': self.parse_number(cost),
|
||
'average': self.parse_number(average),
|
||
'amount': amount,
|
||
'filled': filled,
|
||
'remaining': remaining,
|
||
'status': status,
|
||
'fee': fee,
|
||
'trades': trades,
|
||
})
|
||
|
||
def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetch all unfilled currently open orders
|
||
|
||
https://docs.upbit.com/kr/reference/대기-주문-조회
|
||
https://global-docs.upbit.com/reference/open-order
|
||
|
||
: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
|
||
:param str [params.state]: default is 'wait', set to 'watch' for stop limit orders
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['market'] = market['id']
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
response = self.privateGetOrdersOpen(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
|
||
# "side": "ask",
|
||
# "ord_type": "limit",
|
||
# "price": "1.5",
|
||
# "state": "wait",
|
||
# "market": "SGD-XRP",
|
||
# "created_at": "2024-06-05T09:37:10Z",
|
||
# "volume": "10",
|
||
# "remaining_volume": "10",
|
||
# "reserved_fee": "0",
|
||
# "remaining_fee": "0",
|
||
# "paid_fee": "0",
|
||
# "locked": "10",
|
||
# "executed_volume": "0",
|
||
# "executed_funds": "0",
|
||
# "trades_count": 0
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_orders(response, market, since, limit)
|
||
|
||
def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
||
"""
|
||
fetches information on multiple closed orders made by the user
|
||
|
||
https://docs.upbit.com/kr/reference/종료-주문-조회
|
||
https://global-docs.upbit.com/reference/closed-order
|
||
|
||
: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
|
||
:param int [params.until]: timestamp in ms of the latest order
|
||
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'state': 'done',
|
||
}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['market'] = market['id']
|
||
if since is not None:
|
||
request['start_time'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
request, params = self.handle_until_option('end_time', request, params)
|
||
response = self.privateGetOrdersClosed(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
|
||
# "side": "ask",
|
||
# "ord_type": "limit",
|
||
# "price": "1.5",
|
||
# "state": "done",
|
||
# "market": "SGD-XRP",
|
||
# "created_at": "2024-06-05T09:37:10Z",
|
||
# "volume": "10",
|
||
# "remaining_volume": "10",
|
||
# "reserved_fee": "0",
|
||
# "remaining_fee": "0",
|
||
# "paid_fee": "0",
|
||
# "locked": "10",
|
||
# "executed_volume": "0",
|
||
# "executed_funds": "0",
|
||
# "trades_count": 0,
|
||
# "time_in_force": "ioc"
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_orders(response, market, since, limit)
|
||
|
||
def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
||
"""
|
||
fetches information on multiple canceled orders made by the user
|
||
|
||
https://docs.upbit.com/kr/reference/종료-주문-조회
|
||
https://global-docs.upbit.com/reference/closed-order
|
||
|
||
:param str symbol: unified market symbol of the market orders were made in
|
||
:param int [since]: timestamp in ms of the earliest order, default is None
|
||
:param int [limit]: max number of orders to return, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param int [params.until]: timestamp in ms of the latest order
|
||
:returns dict: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'state': 'cancel',
|
||
}
|
||
market = None
|
||
if symbol is not None:
|
||
market = self.market(symbol)
|
||
request['market'] = market['id']
|
||
if since is not None:
|
||
request['start_time'] = since
|
||
if limit is not None:
|
||
request['limit'] = limit
|
||
request, params = self.handle_until_option('end_time', request, params)
|
||
response = self.privateGetOrdersClosed(self.extend(request, params))
|
||
#
|
||
# [
|
||
# {
|
||
# "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
|
||
# "side": "ask",
|
||
# "ord_type": "limit",
|
||
# "price": "1.5",
|
||
# "state": "cancel",
|
||
# "market": "SGD-XRP",
|
||
# "created_at": "2024-06-05T09:37:10Z",
|
||
# "volume": "10",
|
||
# "remaining_volume": "10",
|
||
# "reserved_fee": "0",
|
||
# "remaining_fee": "0",
|
||
# "paid_fee": "0",
|
||
# "locked": "10",
|
||
# "executed_volume": "0",
|
||
# "executed_funds": "0",
|
||
# "trades_count": 0,
|
||
# "time_in_force": "ioc"
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_orders(response, market, since, limit)
|
||
|
||
def fetch_order(self, id: str, symbol: Str = None, params={}):
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/개별-주문-조회
|
||
https://global-docs.upbit.com/reference/individual-order-inquiry
|
||
|
||
fetches information on an order made by the user
|
||
:param str id: order id
|
||
:param str symbol: not used by upbit fetchOrder
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
||
"""
|
||
self.load_markets()
|
||
request: dict = {
|
||
'uuid': id,
|
||
}
|
||
response = self.privateGetOrder(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee",
|
||
# "side": "bid",
|
||
# "ord_type": "limit",
|
||
# "price": "17417000.0",
|
||
# "state": "done",
|
||
# "market": "KRW-BTC",
|
||
# "created_at": "2018-04-05T14:09:14+09:00",
|
||
# "volume": "1.0",
|
||
# "remaining_volume": "0.0",
|
||
# "reserved_fee": "26125.5",
|
||
# "remaining_fee": "25974.0",
|
||
# "paid_fee": "151.5",
|
||
# "locked": "17341974.0",
|
||
# "executed_volume": "1.0",
|
||
# "trades_count": 2,
|
||
# "trades": [
|
||
# {
|
||
# "market": "KRW-BTC",
|
||
# "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3",
|
||
# "price": "101000.0",
|
||
# "volume": "0.77368323",
|
||
# "funds": "78142.00623",
|
||
# "ask_fee": "117.213009345",
|
||
# "bid_fee": "117.213009345",
|
||
# "created_at": "2018-04-05T14:09:15+09:00",
|
||
# "side": "bid"
|
||
# },
|
||
# {
|
||
# "market": "KRW-BTC",
|
||
# "uuid": "f73da467-c42f-407d-92fa-e10d86450a20",
|
||
# "price": "101000.0",
|
||
# "volume": "0.22631677",
|
||
# "funds": "22857.99377",
|
||
# "ask_fee": "34.286990655",
|
||
# "bid_fee": "34.286990655",
|
||
# "created_at": "2018-04-05T14:09:15+09:00",
|
||
# "side": "bid"
|
||
# }
|
||
# ]
|
||
# }
|
||
#
|
||
return self.parse_order(response)
|
||
|
||
def fetch_deposit_addresses(self, codes: Strings = None, params={}) -> List[DepositAddress]:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/전체-입금-주소-조회
|
||
https://global-docs.upbit.com/reference/general-deposit-address-inquiry
|
||
|
||
fetch deposit addresses for multiple currencies and chain types
|
||
:param str[]|None codes: list of unified currency codes, default is None
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: a list of `address structures <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
self.load_markets()
|
||
response = self.privateGetDepositsCoinAddresses(params)
|
||
#
|
||
# [
|
||
# {
|
||
# "currency": "BTC",
|
||
# "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD",
|
||
# "secondary_address": null
|
||
# },
|
||
# {
|
||
# "currency": "ETH",
|
||
# "deposit_address": "0x0d73e0a482b8cf568976d2e8688f4a899d29301c",
|
||
# "secondary_address": null
|
||
# },
|
||
# {
|
||
# "currency": "XRP",
|
||
# "deposit_address": "rN9qNpgnBaZwqCg8CvUZRPqCcPPY7wfWep",
|
||
# "secondary_address": "3057887915"
|
||
# }
|
||
# ]
|
||
#
|
||
return self.parse_deposit_addresses(response, codes)
|
||
|
||
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
||
#
|
||
# {
|
||
# currency: 'XRP',
|
||
# net_type: 'XRP',
|
||
# deposit_address: 'raQwCVAJVqjrVm1Nj5SFRcX8i22BhdC9WA',
|
||
# secondary_address: '167029435'
|
||
# }
|
||
#
|
||
address = self.safe_string(depositAddress, 'deposit_address')
|
||
tag = self.safe_string(depositAddress, 'secondary_address')
|
||
currencyId = self.safe_string(depositAddress, 'currency')
|
||
code = self.safe_currency_code(currencyId)
|
||
networkId = self.safe_string(depositAddress, 'net_type')
|
||
self.check_address(address)
|
||
return {
|
||
'info': depositAddress,
|
||
'currency': code,
|
||
'network': self.network_id_to_code(networkId),
|
||
'address': address,
|
||
'tag': tag,
|
||
}
|
||
|
||
def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/개별-입금-주소-조회
|
||
https://global-docs.upbit.com/reference/individual-deposit-address-inquiry
|
||
|
||
fetch the deposit address for a currency associated with self account
|
||
:param str code: unified currency code
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:param str params['network']: deposit chain, can view all chains via self.publicGetWalletAssets, default is eth, unless the currency has a default chain within self.options['networks']
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
networkCode = None
|
||
networkCode, params = self.handle_network_code_and_params(params)
|
||
if networkCode is None:
|
||
raise ArgumentsRequired(self.id + ' fetchDepositAddress requires params["network"]')
|
||
response = self.privateGetDepositsCoinAddress(self.extend({
|
||
'currency': currency['id'],
|
||
'net_type': self.network_code_to_id(networkCode, currency['code']),
|
||
}, params))
|
||
#
|
||
# {
|
||
# currency: 'XRP',
|
||
# net_type: 'XRP',
|
||
# deposit_address: 'raQwCVAJVqjrVm1Nj5SFRcX8i22BhdC9WA',
|
||
# secondary_address: '167029435'
|
||
# }
|
||
#
|
||
return self.parse_deposit_address(response)
|
||
|
||
def create_deposit_address(self, code: str, params={}) -> DepositAddress:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/입금-주소-생성-요청
|
||
https://global-docs.upbit.com/reference/deposit-address-generation
|
||
|
||
create a currency deposit address
|
||
:param str code: unified currency code of the currency for the deposit address
|
||
:param dict [params]: extra parameters specific to the exchange API endpoint
|
||
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
||
"""
|
||
self.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'currency': currency['id'],
|
||
}
|
||
# https://github.com/ccxt/ccxt/issues/6452
|
||
response = self.privatePostDepositsGenerateCoinAddress(self.extend(request, params))
|
||
#
|
||
# https://docs.upbit.com/v1.0/reference#%EC%9E%85%EA%B8%88-%EC%A3%BC%EC%86%8C-%EC%83%9D%EC%84%B1-%EC%9A%94%EC%B2%AD
|
||
# can be any of the two responses:
|
||
#
|
||
# {
|
||
# "success" : True,
|
||
# "message" : "Creating BTC deposit address."
|
||
# }
|
||
#
|
||
# {
|
||
# "currency": "BTC",
|
||
# "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD",
|
||
# "secondary_address": null
|
||
# }
|
||
#
|
||
message = self.safe_string(response, 'message')
|
||
if message is not None:
|
||
raise AddressPending(self.id + ' is generating ' + code + ' deposit address, call fetchDepositAddress or createDepositAddress one more time later to retrieve the generated address')
|
||
return self.parse_deposit_address(response)
|
||
|
||
def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
||
"""
|
||
|
||
https://docs.upbit.com/kr/reference/디지털자산-출금하기
|
||
https://global-docs.upbit.com/reference/withdrawal-digital-assets
|
||
|
||
make a withdrawal
|
||
: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.load_markets()
|
||
currency = self.currency(code)
|
||
request: dict = {
|
||
'amount': amount,
|
||
}
|
||
response = None
|
||
if code != 'KRW':
|
||
self.check_address(address)
|
||
# 2023-05-23 Change to required parameters for digital assets
|
||
network = self.safe_string_upper_2(params, 'network', 'net_type')
|
||
if network is None:
|
||
raise ArgumentsRequired(self.id + ' withdraw() requires a network argument')
|
||
params = self.omit(params, ['network'])
|
||
request['net_type'] = network
|
||
request['currency'] = currency['id']
|
||
request['address'] = address
|
||
if tag is not None:
|
||
request['secondary_address'] = tag
|
||
params = self.omit(params, 'network')
|
||
response = self.privatePostWithdrawsCoin(self.extend(request, params))
|
||
else:
|
||
response = self.privatePostWithdrawsKrw(self.extend(request, params))
|
||
#
|
||
# {
|
||
# "type": "withdraw",
|
||
# "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79",
|
||
# "currency": "BTC",
|
||
# "txid": "ebe6937b-130e-4066-8ac6-4b0e67f28adc",
|
||
# "state": "processing",
|
||
# "created_at": "2018-04-13T11:24:01+09:00",
|
||
# "done_at": null,
|
||
# "amount": "0.01",
|
||
# "fee": "0.0",
|
||
# "krw_amount": "80420.0"
|
||
# }
|
||
#
|
||
return self.parse_transaction(response)
|
||
|
||
def nonce(self):
|
||
return self.milliseconds()
|
||
|
||
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
||
url = self.implode_params(self.urls['api'][api], {
|
||
'hostname': self.hostname,
|
||
})
|
||
url += '/' + self.version + '/' + self.implode_params(path, params)
|
||
query = self.omit(params, self.extract_params(path))
|
||
if method != 'POST':
|
||
if query:
|
||
url += '?' + self.urlencode(query)
|
||
if api == 'private':
|
||
self.check_required_credentials()
|
||
headers = {}
|
||
nonce = self.uuid()
|
||
request: dict = {
|
||
'access_key': self.apiKey,
|
||
'nonce': nonce,
|
||
}
|
||
hasQuery = query
|
||
auth = None
|
||
if (method != 'GET') and (method != 'DELETE'):
|
||
body = self.json(params)
|
||
headers['Content-Type'] = 'application/json'
|
||
if hasQuery:
|
||
auth = self.rawencode(query)
|
||
if auth is not None:
|
||
hash = self.hash(self.encode(auth), 'sha512')
|
||
request['query_hash'] = hash
|
||
request['query_hash_alg'] = 'SHA512'
|
||
token = self.jwt(request, self.encode(self.secret), 'sha256')
|
||
headers['Authorization'] = 'Bearer ' + token
|
||
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
|
||
#
|
||
# {'error': {'message': "Missing request parameter error. Check the required parameters!", 'name': 400}},
|
||
# {'error': {'message': "side is missing, side does not have a valid value", 'name': "validation_error"}},
|
||
# {'error': {'message': "개인정보 제 3자 제공 동의가 필요합니다.", 'name': "thirdparty_agreement_required"}},
|
||
# {'error': {'message': "권한이 부족합니다.", 'name': "out_of_scope"}},
|
||
# {'error': {'message': "주문을 찾지 못했습니다.", 'name': "order_not_found"}},
|
||
# {'error': {'message': "주문가능한 금액(ETH)이 부족합니다.", 'name': "insufficient_funds_ask"}},
|
||
# {'error': {'message': "주문가능한 금액(BTC)이 부족합니다.", 'name': "insufficient_funds_bid"}},
|
||
# {'error': {'message': "잘못된 엑세스 키입니다.", 'name': "invalid_access_key"}},
|
||
# {'error': {'message': "Jwt 토큰 검증에 실패했습니다.", 'name': "jwt_verification"}}
|
||
#
|
||
error = self.safe_value(response, 'error')
|
||
if error is not None:
|
||
message = self.safe_string(error, 'message')
|
||
name = self.safe_string(error, 'name')
|
||
feedback = self.id + ' ' + body
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
||
self.throw_exactly_matched_exception(self.exceptions['exact'], name, feedback)
|
||
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
||
self.throw_broadly_matched_exception(self.exceptions['broad'], name, feedback)
|
||
raise ExchangeError(feedback) # unknown message
|
||
return None
|