# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt.async_support.base.exchange import Exchange
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
            },
        })

    async 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
        await self.load_markets()
        currency = self.currency(code)
        return await self.fetch_currency_by_id(currency['id'], params)

    async 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 = await 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,
                },
            },
        }

    async 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
        await self.load_markets()
        market = self.market(symbol)
        return await self.fetch_market_by_id(market['id'], params)

    async 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 = await 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'),
                },
            },
        }

    async def fetch_markets(self, params={}):
        response = await 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

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await 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

    async def fetch_order_books(self, symbols=None, params={}):
        await 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 = await 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

    async def fetch_order_book(self, symbol, limit=None, params={}):
        orderbooks = await 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,
        }

    async def fetch_tickers(self, symbols=None, params={}):
        await 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 = await 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

    async def fetch_ticker(self, symbol, params={}):
        tickers = await 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,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        if limit is None:
            limit = 200
        request = {
            'market': market['id'],
            'count': limit,
        }
        response = await 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
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await 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 = await 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)

    async 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 ')
        await 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 = await 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)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'uuid': id,
        }
        response = await 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)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await 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 = await 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)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await 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 = await 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

    async def fetch_orders_by_state(self, state, symbol=None, since=None, limit=None, params={}):
        await 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 = await 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)

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

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

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

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'uuid': id,
        }
        response = await 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

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

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

    async def create_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await 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)

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await 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 = await 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
