# -*- 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
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound


class upbit (Exchange):

    def describe(self):
        return self.deep_extend(super(upbit, self).describe(), {
            'id': 'upbit',
            'name': 'Upbit',
            'countries': ['KR'],
            'version': 'v1',
            'rateLimit': 1000,
            'certified': True,
            # new metainfo interface
            'has': {
                'CORS': True,
                'createDepositAddress': True,
                'createMarketOrder': True,
                'fetchDepositAddress': True,
                'fetchClosedOrders': True,
                'fetchMyTrades': False,
                'fetchOHLCV': True,
                'fetchOrder': True,
                'fetchOrderBooks': True,
                'fetchOpenOrders': True,
                'fetchOrders': False,
                'fetchTickers': True,
                'withdraw': True,
                'fetchDeposits': True,
                'fetchWithdrawals': True,
                'fetchTransactions': False,
            },
            'timeframes': {
                '1m': 'minutes',
                '3m': 'minutes',
                '5m': 'minutes',
                '15m': 'minutes',
                '30m': 'minutes',
                '1h': 'minutes',
                '4h': 'minutes',
                '1d': 'days',
                '1w': 'weeks',
                '1M': 'months',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg',
                'api': 'https://api.upbit.com',
                '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': {
                'public': {
                    'get': [
                        'market/all',
                        'candles/{timeframe}',
                        'candles/{timeframe}/{unit}',
                        'candles/minutes/{unit}',
                        'candles/minutes/1',
                        'candles/minutes/3',
                        'candles/minutes/5',
                        'candles/minutes/15',
                        'candles/minutes/30',
                        'candles/minutes/60',
                        'candles/minutes/240',
                        'candles/days',
                        'candles/weeks',
                        'candles/months',
                        'trades/ticks',
                        'ticker',
                        'orderbook',
                    ],
                },
                'private': {
                    'get': [
                        'accounts',
                        'orders/chance',
                        'order',
                        'orders',
                        'withdraws',
                        'withdraw',
                        'withdraws/chance',
                        'deposits',
                        'deposit',
                        'deposits/coin_addresses',
                        'deposits/coin_address',
                    ],
                    'post': [
                        'orders',
                        'withdraws/coin',
                        'withdraws/krw',
                        'deposits/generate_coin_address',
                    ],
                    'delete': [
                        'order',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.0025,
                    'taker': 0.0025,
                },
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {},
                    'deposit': {},
                },
            },
            'exceptions': {
                'exact': {
                    'Missing request parameter error. Check the required parametersnot ': 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,
                'fetchTickersMaxLength': 4096,  # 2048,
                'fetchOrderBooksMaxLength': 4096,  # 2048,
                'symbolSeparator': '-',
                'tradingFeesByQuoteCurrency': {
                    'KRW': 0.0005,
                },
            },
            'commonCurrencies': {
                'CPT': 'Contents Protocol',  # conflict with CPT(Cryptaur) https://github.com/ccxt/ccxt/issues/4920
            },
        })

    def fetch_currency(self, code, 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, params={}):
        # self method is for retrieving funding fees and limits per currency
        # it requires private access and API keys properly set up
        request = {
            '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 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_float(withdrawLimits, 'onetime')
        maxDailyWithdrawal = self.safe_float(withdrawLimits, 'daily', maxOnetimeWithdrawal)
        remainingDailyWithdrawal = self.safe_float(withdrawLimits, 'remaining_daily', maxDailyWithdrawal)
        maxWithdrawLimit = None
        if remainingDailyWithdrawal > 0:
            maxWithdrawLimit = remainingDailyWithdrawal
        else:
            maxWithdrawLimit = maxDailyWithdrawal
        precision = None
        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_float(currencyInfo, 'withdraw_fee'),
            'precision': precision,
            'limits': {
                'withdraw': {
                    'min': self.safe_float(withdrawLimits, 'minimum'),
                    'max': maxWithdrawLimit,
                },
            },
        }

    def fetch_market(self, symbol, 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, params={}):
        # self method is for retrieving trading fees and limits per market
        # it requires private access and API keys properly set up
        request = {
            'market': id,
        }
        response = self.privateGetOrdersChance(self.extend(request, params))
        #
        #     {    bid_fee:   "0.0005",
        #           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:  1000  },
        #                              ask: {  currency: "BTC",
        #                                     price_unit:  null,
        #                                      min_total:  1000  },
        #                        max_total:   "1000000000.0",
        #                            state:   "active"              },
        #       bid_account: {         currency: "KRW",
        #                                balance: "0.0",
        #                                 locked: "0.0",
        #                      avg_krw_buy_price: "0",
        #                               modified:  False},
        #       ask_account: {         currency: "BTC",
        #                                balance: "0.00780836",
        #                                 locked: "0.0",
        #                      avg_krw_buy_price: "6465564.67",
        #                               modified:  False        }      }
        #
        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)
        symbol = base + '/' + quote
        precision = {
            'amount': 8,
            'price': 8,
        }
        state = self.safe_string(marketInfo, 'state')
        active = (state == 'active')
        bidFee = self.safe_float(response, 'bid_fee')
        askFee = self.safe_float(response, 'ask_fee')
        fee = max(bidFee, askFee)
        return {
            'info': response,
            'id': marketId,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'baseId': baseId,
            'quoteId': quoteId,
            'active': active,
            'precision': precision,
            'maker': fee,
            'taker': fee,
            'limits': {
                'amount': {
                    'min': self.safe_float(ask, 'min_total'),
                    'max': None,
                },
                'price': {
                    'min': math.pow(10, -precision['price']),
                    'max': None,
                },
                'cost': {
                    'min': self.safe_float(bid, 'min_total'),
                    'max': self.safe_float(marketInfo, 'max_total'),
                },
            },
        }

    def fetch_markets(self, params={}):
        response = self.publicGetMarketAll(params)
        #
        #     [{      market: "KRW-BTC",
        #          korean_name: "비트코인",
        #         english_name: "Bitcoin"  },
        #       {      market: "KRW-DASH",
        #          korean_name: "대시",
        #         english_name: "Dash"      },
        #       {      market: "KRW-ETH",
        #          korean_name: "이더리움",
        #         english_name: "Ethereum"},
        #       {      market: "BTC-ETH",
        #          korean_name: "이더리움",
        #         english_name: "Ethereum"},
        #       ...,
        #       {      market: "BTC-BSV",
        #          korean_name: "비트코인에스브이",
        #         english_name: "Bitcoin SV"}]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'market')
            quoteId, baseId = id.split('-')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'amount': 8,
                'price': 8,
            }
            active = True
            makerFee = self.safe_float(self.options['tradingFeesByQuoteCurrency'], quote, self.fees['trading']['maker'])
            takerFee = self.safe_float(self.options['tradingFeesByQuoteCurrency'], quote, self.fees['trading']['taker'])
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'info': market,
                'precision': precision,
                'maker': makerFee,
                'taker': takerFee,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision['amount']),
                        'max': None,
                    },
                    'price': {
                        'min': math.pow(10, -precision['price']),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
            })
        return result

    def fetch_balance(self, params={}):
        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    }   ]
        #
        result = {'info': response}
        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_float(balance, 'balance')
            account['used'] = self.safe_float(balance, 'locked')
            result[code] = account
        return self.parse_balance(result)

    def get_symbol_from_market_id(self, marketId, market=None):
        if marketId is None:
            return None
        market = self.safe_value(self.markets_by_id, marketId, market)
        if market is not None:
            return market['symbol']
        baseId, quoteId = marketId.split(self.options['symbolSeparator'])
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        return base + '/' + quote

    def fetch_order_books(self, symbols=None, params={}):
        self.load_markets()
        ids = None
        if symbols is None:
            ids = ','.join(self.ids)
            # max URL length is 2083 symbols, including http schema, hostname, tld, etc...
            if len(ids) > self.options['fetchOrderBooksMaxLength']:
                numIds = len(self.ids)
                raise ExchangeError(self.id + ' has ' + str(numIds) + ' symbols(' + str(len(ids)) + ' characters) exceeding max URL length(' + str(self.options['fetchOrderBooksMaxLength']) + ' characters), you are required to specify a list of symbols in the first argument to fetchOrderBooks')
        else:
            ids = self.market_ids(symbols)
            ids = ','.join(ids)
        request = {
            'markets': ids,
        }
        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 = {}
        for i in range(0, len(response)):
            orderbook = response[i]
            symbol = self.get_symbol_from_market_id(self.safe_string(orderbook, 'market'))
            timestamp = self.safe_integer(orderbook, 'timestamp')
            result[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, limit=None, params={}):
        orderbooks = self.fetch_order_books([symbol], params)
        return self.safe_value(orderbooks, symbol)

    def parse_ticker(self, ticker, market=None):
        #
        #       {               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  }
        #
        timestamp = self.safe_integer(ticker, 'trade_timestamp')
        symbol = self.get_symbol_from_market_id(self.safe_string(ticker, 'market'), market)
        previous = self.safe_float(ticker, 'prev_closing_price')
        last = self.safe_float(ticker, 'trade_price')
        change = self.safe_float(ticker, 'signed_change_price')
        percentage = self.safe_float(ticker, 'signed_change_rate')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high_price'),
            'low': self.safe_float(ticker, 'low_price'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': self.safe_float(ticker, 'opening_price'),
            'close': last,
            'last': last,
            'previousClose': previous,
            'change': change,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_float(ticker, 'acc_trade_volume_24h'),
            'quoteVolume': self.safe_float(ticker, 'acc_trade_price_24h'),
            'info': ticker,
        }

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        ids = None
        if symbols is None:
            ids = ','.join(self.ids)
            # max URL length is 2083 symbols, including http schema, hostname, tld, etc...
            if len(ids) > self.options['fetchTickersMaxLength']:
                numIds = len(self.ids)
                raise ExchangeError(self.id + ' has ' + str(numIds) + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchTickers')
        else:
            ids = self.market_ids(symbols)
            ids = ','.join(ids)
        request = {
            '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 = {}
        for t in range(0, len(response)):
            ticker = self.parse_ticker(response[t])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return result

    def fetch_ticker(self, symbol, params={}):
        tickers = self.fetch_tickers([symbol], params)
        return self.safe_value(tickers, symbol)

    def parse_trade(self, trade, market=None):
        #
        # 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_float(trade, 'funds')
        price = self.safe_float_2(trade, 'trade_price', 'price')
        amount = self.safe_float_2(trade, 'trade_volume', 'volume')
        if cost is None:
            if amount is not None:
                if price is not None:
                    cost = price * amount
        marketId = self.safe_string(trade, 'market')
        market = self.safe_value(self.markets_by_id, marketId)
        fee = None
        feeCurrency = None
        symbol = None
        if market is not None:
            symbol = market['symbol']
            feeCurrency = market['quote']
        else:
            baseId, quoteId = marketId.split('-')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            feeCurrency = quote
        feeCost = self.safe_string(trade, askOrBid + '_fee')
        if feeCost is not None:
            fee = {
                'currency': feeCurrency,
                'cost': feeCost,
            }
        return {
            'id': id,
            'info': trade,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': 'limit',
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        if limit is None:
            limit = 200
        request = {
            '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 parse_ohlcv(self, ohlcv, market=None, timeframe='1d', since=None, limit=None):
        #
        #       {                 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_float(ohlcv, 'opening_price'),
            self.safe_float(ohlcv, 'high_price'),
            self.safe_float(ohlcv, 'low_price'),
            self.safe_float(ohlcv, 'trade_price'),
            self.safe_float(ohlcv, 'candle_acc_trade_volume'),  # base volume
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        timeframePeriod = self.parse_timeframe(timeframe)
        timeframeValue = self.timeframes[timeframe]
        if limit is None:
            limit = 200
        request = {
            'market': market['id'],
            'timeframe': timeframeValue,
            'count': limit,
        }
        method = 'publicGetCandlesTimeframe'
        if timeframeValue == 'minutes':
            numMinutes = int(round(timeframePeriod / 60))
            request['unit'] = numMinutes
            method += 'Unit'
        if since is not None:
            # convert `since` to `to` value
            request['to'] = self.iso8601(self.sum(since, timeframePeriod * limit * 1000))
        response = getattr(self, method)(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 create_order(self, symbol, type, side, amount, price=None, params={}):
        if type == 'market':
            # for market buy it requires the amount of quote currency to spend
            if side == 'buy':
                if self.options['createMarketBuyOrderRequiresPrice']:
                    if price is None:
                        raise InvalidOrder(self.id + " createOrder() requires the price argument with market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = False to supply the cost in the amount argument(the exchange-specific behaviour)")
                    else:
                        amount = amount * price
        orderSide = None
        if side == 'buy':
            orderSide = 'bid'
        elif side == 'sell':
            orderSide = 'ask'
        else:
            raise InvalidOrder(self.id + ' createOrder allows buy or sell side onlynot ')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
            'side': orderSide,
        }
        if type == 'limit':
            request['volume'] = self.amount_to_precision(symbol, amount)
            request['price'] = self.price_to_precision(symbol, price)
            request['ord_type'] = type
        elif type == 'market':
            if side == 'buy':
                request['ord_type'] = 'price'
                request['price'] = self.price_to_precision(symbol, amount)
            elif side == 'sell':
                request['ord_type'] = type
                request['volume'] = self.amount_to_precision(symbol, amount)
        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, symbol=None, params={}):
        self.load_markets()
        request = {
            '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 fetch_deposits(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # '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.parseTransactions(response, currency, since, limit)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # '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.parseTransactions(response, currency, since, limit)

    def parse_transaction_status(self, status):
        statuses = {
            'ACCEPTED': 'ok',  # deposits
            # withdrawals:
            'submitting': 'pending',  # 처리 중
            'submitted': 'pending',  # 처리 완료
            'almost_accepted': 'pending',  # 출금대기중
            'rejected': 'failed',  # 거부
            'accepted': 'pending',  # 승인됨
            'processing': 'pending',  # 처리 중
            'done': 'ok',  # 완료
            'canceled': 'canceled',  # 취소됨
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #         "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
        #
        #     {
        #         "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"
        #     }
        #
        id = self.safe_string(transaction, 'uuid')
        amount = self.safe_float(transaction, 'amount')
        address = None  # not present in the data structure received from the exchange
        tag = None  # not present in the data structure received from the exchange
        txid = self.safe_string(transaction, 'txid')
        updated = self.parse8601(self.safe_string(transaction, 'done_at'))
        timestamp = self.parse8601(self.safe_string(transaction, 'created_at', updated))
        type = self.safe_string(transaction, 'type')
        if type == 'withdraw':
            type = 'withdrawal'
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId)
        status = self.parse_transaction_status(self.safe_string(transaction, 'state'))
        feeCost = self.safe_float(transaction, 'fee')
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'address': address,
            'tag': tag,
            'status': status,
            'type': type,
            'updated': updated,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': {
                'currency': code,
                'cost': feeCost,
            },
        }

    def parse_order_status(self, status):
        statuses = {
            'wait': 'open',
            'done': 'closed',
            'cancel': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        #     {
        #         "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",  # missing in market orders
        #                 "bid_fee": "34.286990655",  # missing in market orders
        #                 "created_at": "2018-04-05T14:09:15+09:00",  # missing in market orders
        #                 "side": "bid",
        #             },
        #         ],
        #     }
        #
        id = self.safe_string(order, 'uuid')
        side = self.safe_string(order, 'side')
        if side == 'bid':
            side = 'buy'
        else:
            side = 'sell'
        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_float(order, 'price')
        amount = self.safe_float(order, 'volume')
        remaining = self.safe_float(order, 'remaining_volume')
        filled = self.safe_float(order, 'executed_volume')
        cost = None
        if type == 'price':
            type = 'market'
            cost = price
            price = None
        average = None
        fee = None
        feeCost = self.safe_float(order, 'paid_fee')
        feeCurrency = None
        marketId = self.safe_string(order, 'market')
        market = self.safe_value(self.markets_by_id, marketId)
        symbol = None
        if market is not None:
            symbol = market['symbol']
            feeCurrency = market['quote']
        else:
            baseId, quoteId = marketId.split('-')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            feeCurrency = quote
        trades = self.safe_value(order, 'trades', [])
        trades = self.parse_trades(trades, market, None, None, {'order': id})
        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 = self.sum(cost, trade['cost'])
                if getFeesFromTrades:
                    tradeFee = self.safe_value(trades[i], 'fee', {})
                    tradeFeeCost = self.safe_float(tradeFee, 'cost')
                    if tradeFeeCost is not None:
                        feeCost = self.sum(feeCost, tradeFeeCost)
            average = cost / filled
        if feeCost is not None:
            fee = {
                'currency': feeCurrency,
                'cost': feeCost,
            }
        result = {
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'cost': cost,
            'average': average,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': trades,
        }
        return result

    def fetch_orders_by_state(self, state, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'market': self.market_id(symbol),
            'state': state,
            # 'page': 1,
            # 'order_by': 'asc',
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market'] = market['id']
        response = self.privateGetOrders(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
        #         },
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        return self.fetch_orders_by_state('wait', symbol, since, limit, params)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        return self.fetch_orders_by_state('done', symbol, since, limit, params)

    def fetch_canceled_orders(self, symbol=None, since=None, limit=None, params={}):
        return self.fetch_orders_by_state('cancel', symbol, since, limit, params)

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            '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 parse_deposit_addresses(self, addresses):
        result = []
        for i in range(0, len(addresses)):
            result.append(self.parse_deposit_address(addresses[i]))
        return result

    def fetch_deposit_addresses(self, codes=None, params={}):
        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)

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     {
        #         "currency": "BTC",
        #         "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD",
        #         "secondary_address": null
        #     }
        #
        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)
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': depositAddress,
        }

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        response = self.privateGetDepositsCoinAddress(self.extend({
            'currency': currency['id'],
        }, params))
        #
        #     {
        #         "currency": "BTC",
        #         "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD",
        #         "secondary_address": null
        #     }
        #
        return self.parse_deposit_address(response)

    def create_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.fetch_deposit_address(code, 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:
            return {
                'currency': code,
                'address': None,
                'tag': None,
                'info': response,
            }
        return self.parse_deposit_address(response)

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        request = {
            'amount': amount,
        }
        method = 'privatePostWithdraws'
        if code != 'KRW':
            method += 'Coin'
            request['currency'] = currency['id']
            request['address'] = address
            if tag is not None:
                request['secondary_address'] = tag
        else:
            method += 'Krw'
        response = getattr(self, method)(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.urls['api'] + '/' + self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if method == 'GET':
            if query:
                url += '?' + self.urlencode(query)
        if api == 'private':
            self.check_required_credentials()
            nonce = self.nonce()
            request = {
                'access_key': self.apiKey,
                'nonce': nonce,
            }
            if query:
                request['query'] = self.urlencode(query)
            jwt = self.jwt(request, self.encode(self.secret))
            headers = {
                'Authorization': 'Bearer ' + jwt,
            }
            if method != 'GET':
                body = self.json(params)
                headers['Content-Type'] = 'application/json'
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        #
        #   {'error': {'message': "Missing request parameter error. Check the required parametersnot ", '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 + ' ' + self.json(response)
            exact = self.exceptions['exact']
            if message in exact:
                raise exact[message](feedback)
            if name in exact:
                raise exact[name](feedback)
            broad = self.exceptions['broad']
            broadKey = self.findBroadlyMatchedKey(broad, message)
            if broadKey is not None:
                raise broad[broadKey](feedback)
            broadKey = self.findBroadlyMatchedKey(broad, name)
            if broadKey is not None:
                raise broad[broadKey](feedback)
            raise ExchangeError(feedback)  # unknown message
