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

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

from ccxt_zagrava.base.exchange import Exchange
from ccxt_zagrava.abstract.upbit import ImplicitAPI
from ccxt_zagrava.base.types import Balances, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import AddressPending
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class upbit(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(upbit, self).describe(), {
            'id': 'upbit',
            'name': 'Upbit',
            'countries': ['KR'],
            'version': 'v1',
            'rateLimit': 1000,
            'pro': True,
            # new metainfo interface
            'has': {
                'CORS': True,
                'spot': True,
                'margin': None,
                'swap': False,
                'future': False,
                'option': False,
                'cancelOrder': True,
                'createDepositAddress': True,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrder': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'fetchBalance': True,
                'fetchCanceledOrders': True,
                'fetchClosedOrders': True,
                'fetchDeposit': True,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': True,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': False,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': True,
                'fetchOrders': False,
                'fetchPositionMode': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': False,
                'fetchTransactions': False,
                'fetchWithdrawal': True,
                'fetchWithdrawals': True,
                'transfer': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': 'minutes',
                '3m': 'minutes',
                '5m': 'minutes',
                '10m': 'minutes',
                '15m': 'minutes',
                '30m': 'minutes',
                '1h': 'minutes',
                '4h': 'minutes',
                '1d': 'days',
                '1w': 'weeks',
                '1M': 'months',
            },
            'hostname': 'api.upbit.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/49245610-eeaabe00-f423-11e8-9cba-4b0aed794799.jpg',
                'api': {
                    'public': 'https://{hostname}',
                    'private': 'https://{hostname}',
                },
                'www': 'https://upbit.com',
                'doc': 'https://docs.upbit.com/docs/%EC%9A%94%EC%B2%AD-%EC%88%98-%EC%A0%9C%ED%95%9C',
                'fees': 'https://upbit.com/service_center/guide',
            },
            'api': {
                'public': {
                    'get': [
                        'market/all',
                        'candles/{timeframe}',
                        'candles/{timeframe}/{unit}',
                        'candles/minutes/{unit}',
                        'candles/minutes/1',
                        'candles/minutes/3',
                        'candles/minutes/5',
                        'candles/minutes/10',
                        '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',
                        'orders/closed',
                        'orders/open',
                        'orders/uuids',
                        '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': self.parse_number('0.0025'),
                    'taker': self.parse_number('0.0025'),
                },
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {},
                    'deposit': {},
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    'This key has expired.': AuthenticationError,
                    'Missing request parameter error. Check the required parameters!': BadRequest,
                    'side is missing, side does not have a valid value': InvalidOrder,
                },
                'broad': {
                    'thirdparty_agreement_required': PermissionDenied,
                    'out_of_scope': PermissionDenied,
                    'order_not_found': OrderNotFound,
                    'insufficient_funds': InsufficientFunds,
                    'invalid_access_key': AuthenticationError,
                    'jwt_verification': AuthenticationError,
                    'create_ask_error': ExchangeError,
                    'create_bid_error': ExchangeError,
                    'volume_too_large': InvalidOrder,
                    'invalid_funds': InvalidOrder,
                },
            },
            'options': {
                'createMarketBuyOrderRequiresPrice': True,
                'fetchTickersMaxLength': 4096,  # 2048,
                'fetchOrderBooksMaxLength': 4096,  # 2048,
                'tradingFeesByQuoteCurrency': {
                    'KRW': 0.0005,
                },
            },
            'commonCurrencies': {
                'TON': 'Tokamak Network',
            },
        })

    def fetch_currency(self, code: str, params={}):
        # self method is for retrieving funding fees and limits per currency
        # it requires private access and API keys properly set up
        self.load_markets()
        currency = self.currency(code)
        return self.fetch_currency_by_id(currency['id'], params)

    def fetch_currency_by_id(self, id: str, params={}):
        # self method is for retrieving funding fees and limits per currency
        # it requires private access and API keys properly set up
        request: dict = {
            'currency': id,
        }
        response = self.privateGetWithdrawsChance(self.extend(request, params))
        #
        #     {
        #         "member_level": {
        #             "security_level": 3,
        #             "fee_level": 0,
        #             "email_verified": True,
        #             "identity_auth_verified": True,
        #             "bank_account_verified": True,
        #             "kakao_pay_auth_verified": False,
        #             "locked": False,
        #             "wallet_locked": False
        #         },
        #         "currency": {
        #             "code": "BTC",
        #             "withdraw_fee": "0.0005",
        #             "is_coin": True,
        #             "wallet_state": "working",
        #             "wallet_support": ["deposit", "withdraw"]
        #         },
        #         "account": {
        #             "currency": "BTC",
        #             "balance": "10.0",
        #             "locked": "0.0",
        #             "avg_krw_buy_price": "8042000",
        #             "modified": False
        #         },
        #         "withdraw_limit": {
        #             "currency": "BTC",
        #             "minimum": null,
        #             "onetime": null,
        #             "daily": "10.0",
        #             "remaining_daily": "10.0",
        #             "remaining_daily_krw": "0.0",
        #             "fixed": null,
        #             "can_withdraw": True
        #         }
        #     }
        #
        memberInfo = self.safe_value(response, 'member_level', {})
        currencyInfo = self.safe_value(response, 'currency', {})
        withdrawLimits = self.safe_value(response, 'withdraw_limit', {})
        canWithdraw = self.safe_value(withdrawLimits, 'can_withdraw')
        walletState = self.safe_string(currencyInfo, 'wallet_state')
        walletLocked = self.safe_value(memberInfo, 'wallet_locked')
        locked = self.safe_value(memberInfo, 'locked')
        active = True
        if (canWithdraw is not None) and not canWithdraw:
            active = False
        elif walletState != 'working':
            active = False
        elif (walletLocked is not None) and walletLocked:
            active = False
        elif (locked is not None) and locked:
            active = False
        maxOnetimeWithdrawal = self.safe_string(withdrawLimits, 'onetime')
        maxDailyWithdrawal = self.safe_string(withdrawLimits, 'daily', maxOnetimeWithdrawal)
        remainingDailyWithdrawal = self.safe_string(withdrawLimits, 'remaining_daily', maxDailyWithdrawal)
        maxWithdrawLimit = None
        if Precise.string_gt(remainingDailyWithdrawal, '0'):
            maxWithdrawLimit = remainingDailyWithdrawal
        else:
            maxWithdrawLimit = maxDailyWithdrawal
        currencyId = self.safe_string(currencyInfo, 'code')
        code = self.safe_currency_code(currencyId)
        return {
            'info': response,
            'id': currencyId,
            'code': code,
            'name': code,
            'active': active,
            'fee': self.safe_number(currencyInfo, 'withdraw_fee'),
            'precision': None,
            'limits': {
                'withdraw': {
                    'min': self.safe_number(withdrawLimits, 'minimum'),
                    'max': self.parse_number(maxWithdrawLimit),
                },
            },
        }

    def fetch_market(self, symbol: str, params={}):
        # self method is for retrieving trading fees and limits per market
        # it requires private access and API keys properly set up
        self.load_markets()
        market = self.market(symbol)
        return self.fetch_market_by_id(market['id'], params)

    def fetch_market_by_id(self, id: str, params={}):
        # self method is for retrieving trading fees and limits per market
        # it requires private access and API keys properly set up
        request: dict = {
            'market': id,
        }
        response = self.privateGetOrdersChance(self.extend(request, params))
        #
        #     {
        #         "bid_fee": "0.0015",
        #         "ask_fee": "0.0015",
        #         "market": {
        #             "id": "KRW-BTC",
        #             "name": "BTC/KRW",
        #             "order_types": ["limit"],
        #             "order_sides": ["ask", "bid"],
        #             "bid": {"currency": "KRW", "price_unit": null, "min_total": 1000},
        #             "ask": {"currency": "BTC", "price_unit": null, "min_total": 1000},
        #             "max_total": "100000000.0",
        #             "state": "active",
        #         },
        #         "bid_account": {
        #             "currency": "KRW",
        #             "balance": "0.0",
        #             "locked": "0.0",
        #             "avg_buy_price": "0",
        #             "avg_buy_price_modified": False,
        #             "unit_currency": "KRW",
        #         },
        #         "ask_account": {
        #             "currency": "BTC",
        #             "balance": "10.0",
        #             "locked": "0.0",
        #             "avg_buy_price": "8042000",
        #             "avg_buy_price_modified": False,
        #             "unit_currency": "KRW",
        #         }
        #     }
        #
        marketInfo = self.safe_value(response, 'market')
        bid = self.safe_value(marketInfo, 'bid')
        ask = self.safe_value(marketInfo, 'ask')
        marketId = self.safe_string(marketInfo, 'id')
        baseId = self.safe_string(ask, 'currency')
        quoteId = self.safe_string(bid, 'currency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        state = self.safe_string(marketInfo, 'state')
        bidFee = self.safe_string(response, 'bid_fee')
        askFee = self.safe_string(response, 'ask_fee')
        fee = self.parse_number(Precise.string_max(bidFee, askFee))
        return self.safe_market_structure({
            'id': marketId,
            'symbol': base + '/' + quote,
            'base': base,
            'quote': quote,
            'settle': None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': None,
            'type': 'spot',
            'spot': True,
            'margin': False,
            'swap': False,
            'future': False,
            'option': False,
            'active': (state == 'active'),
            'contract': False,
            'linear': None,
            'inverse': None,
            'taker': fee,
            'maker': fee,
            'contractSize': None,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'precision': {
                'amount': self.parse_number('1e-8'),
                'price': self.parse_number('1e-8'),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': self.safe_number(ask, 'min_total'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.safe_number(bid, 'min_total'),
                    'max': self.safe_number(marketInfo, 'max_total'),
                },
                'info': response,
            },
        })

    def fetch_markets(self, params={}) -> List[Market]:
        """
        :see: https://docs.upbit.com/reference/%EB%A7%88%EC%BC%93-%EC%BD%94%EB%93%9C-%EC%A1%B0%ED%9A%8C
        retrieves data on all markets for upbit
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = self.publicGetMarketAll(params)
        #
        #    [
        #        {
        #            "market": "KRW-BTC",
        #            "korean_name": "비트코인",
        #            "english_name": "Bitcoin"
        #        },
        #        ...,
        #    ]
        #
        return self.parse_markets(response)

    def parse_market(self, market: dict) -> Market:
        id = self.safe_string(market, 'market')
        quoteId, baseId = id.split('-')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        return self.safe_market_structure({
            'id': id,
            'symbol': base + '/' + quote,
            'base': base,
            'quote': quote,
            'settle': None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': None,
            'type': 'spot',
            'spot': True,
            'margin': False,
            'swap': False,
            'future': False,
            'option': False,
            'active': True,
            'contract': False,
            'linear': None,
            'inverse': None,
            'taker': self.safe_number(self.options['tradingFeesByQuoteCurrency'], quote, self.fees['trading']['taker']),
            'maker': self.safe_number(self.options['tradingFeesByQuoteCurrency'], quote, self.fees['trading']['maker']),
            'contractSize': None,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'precision': {
                'price': self.parse_number('1e-8'),
                'amount': self.parse_number('1e-8'),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
            'created': None,
            'info': market,
        })

    def parse_balance(self, response) -> Balances:
        result: dict = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'balance')
            account['used'] = self.safe_string(balance, 'locked')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}) -> Balances:
        """
        :see: https://docs.upbit.com/reference/%EC%A0%84%EC%B2%B4-%EA%B3%84%EC%A2%8C-%EC%A1%B0%ED%9A%8C
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        self.load_markets()
        response = self.privateGetAccounts(params)
        #
        #     [{         currency: "BTC",
        #                   "balance": "0.005",
        #                    "locked": "0.0",
        #         "avg_krw_buy_price": "7446000",
        #                  "modified":  False     },
        #       {         currency: "ETH",
        #                   "balance": "0.1",
        #                    "locked": "0.0",
        #         "avg_krw_buy_price": "250000",
        #                  "modified":  False    }   ]
        #
        return self.parse_balance(response)

    def fetch_order_books(self, symbols: Strings = None, limit: Int = None, params={}):
        """
        :see: https://docs.upbit.com/reference/%ED%98%B8%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data for multiple markets
        :param str[]|None symbols: list of unified market symbols, all symbols fetched if None, default is None
        :param int [limit]: not used by upbit fetchOrderBooks()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbol
        """
        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 + ' fetchOrderBooks() 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: dict = {
            '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: dict = {}
        for i in range(0, len(response)):
            orderbook = response[i]
            marketId = self.safe_string(orderbook, 'market')
            symbol = self.safe_symbol(marketId, None, '-')
            timestamp = self.safe_integer(orderbook, 'timestamp')
            result[symbol] = {
                'symbol': symbol,
                'bids': self.sort_by(self.parse_bids_asks(orderbook['orderbook_units'], 'bid_price', 'bid_size'), 0, True),
                'asks': self.sort_by(self.parse_bids_asks(orderbook['orderbook_units'], 'ask_price', 'ask_size'), 0),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
                'nonce': None,
            }
        return result

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        :see: https://docs.upbit.com/reference/%ED%98%B8%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        orderbooks = self.fetch_order_books([symbol], limit, params)
        return self.safe_value(orderbooks, symbol)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #       {               market: "BTC-ETH",
        #                    "trade_date": "20181122",
        #                    "trade_time": "104543",
        #                "trade_date_kst": "20181122",
        #                "trade_time_kst": "194543",
        #               "trade_timestamp":  1542883543096,
        #                 "opening_price":  0.02976455,
        #                    "high_price":  0.02992577,
        #                     "low_price":  0.02934283,
        #                   "trade_price":  0.02947773,
        #            "prev_closing_price":  0.02966,
        #                        "change": "FALL",
        #                  "change_price":  0.00018227,
        #                   "change_rate":  0.0061453136,
        #           "signed_change_price":  -0.00018227,
        #            "signed_change_rate":  -0.0061453136,
        #                  "trade_volume":  1.00000005,
        #               "acc_trade_price":  100.95825586,
        #           "acc_trade_price_24h":  289.58650166,
        #              "acc_trade_volume":  3409.85311036,
        #          "acc_trade_volume_24h":  9754.40510513,
        #         "highest_52_week_price":  0.12345678,
        #          "highest_52_week_date": "2018-02-01",
        #          "lowest_52_week_price":  0.023936,
        #           "lowest_52_week_date": "2017-12-08",
        #                     "timestamp":  1542883543813  }
        #
        timestamp = self.safe_integer(ticker, 'trade_timestamp')
        marketId = self.safe_string_2(ticker, 'market', 'code')
        market = self.safe_market(marketId, market, '-')
        last = self.safe_string(ticker, 'trade_price')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high_price'),
            'low': self.safe_string(ticker, 'low_price'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': self.safe_string(ticker, 'opening_price'),
            'close': last,
            'last': last,
            'previousClose': self.safe_string(ticker, 'prev_closing_price'),
            'change': self.safe_string(ticker, 'signed_change_price'),
            'percentage': self.safe_string(ticker, 'signed_change_rate'),
            'average': None,
            'baseVolume': self.safe_string(ticker, 'acc_trade_volume_24h'),
            'quoteVolume': self.safe_string(ticker, 'acc_trade_price_24h'),
            'info': ticker,
        }, market)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        :see: https://docs.upbit.com/reference/ticker%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        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 + ' fetchTickers() 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: dict = {
            'markets': ids,
        }
        response = self.publicGetTicker(self.extend(request, params))
        #
        #     [{               market: "BTC-ETH",
        #                    "trade_date": "20181122",
        #                    "trade_time": "104543",
        #                "trade_date_kst": "20181122",
        #                "trade_time_kst": "194543",
        #               "trade_timestamp":  1542883543097,
        #                 "opening_price":  0.02976455,
        #                    "high_price":  0.02992577,
        #                     "low_price":  0.02934283,
        #                   "trade_price":  0.02947773,
        #            "prev_closing_price":  0.02966,
        #                        "change": "FALL",
        #                  "change_price":  0.00018227,
        #                   "change_rate":  0.0061453136,
        #           "signed_change_price":  -0.00018227,
        #            "signed_change_rate":  -0.0061453136,
        #                  "trade_volume":  1.00000005,
        #               "acc_trade_price":  100.95825586,
        #           "acc_trade_price_24h":  289.58650166,
        #              "acc_trade_volume":  3409.85311036,
        #          "acc_trade_volume_24h":  9754.40510513,
        #         "highest_52_week_price":  0.12345678,
        #          "highest_52_week_date": "2018-02-01",
        #          "lowest_52_week_price":  0.023936,
        #           "lowest_52_week_date": "2017-12-08",
        #                     "timestamp":  1542883543813  }]
        #
        result: dict = {}
        for t in range(0, len(response)):
            ticker = self.parse_ticker(response[t])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        :see: https://docs.upbit.com/reference/ticker%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        tickers = self.fetch_tickers([symbol], params)
        return self.safe_value(tickers, symbol)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades
        #
        #       {            market: "BTC-ETH",
        #             "trade_date_utc": "2018-11-22",
        #             "trade_time_utc": "13:55:24",
        #                  "timestamp":  1542894924397,
        #                "trade_price":  0.02914289,
        #               "trade_volume":  0.20074397,
        #         "prev_closing_price":  0.02966,
        #               "change_price":  -0.00051711,
        #                    "ask_bid": "ASK",
        #              "sequential_id":  15428949259430000}
        #
        # fetchOrder trades
        #
        #         {
        #             "market": "KRW-BTC",
        #             "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3",
        #             "price": "101000.0",
        #             "volume": "0.77368323",
        #             "funds": "78142.00623",
        #             "ask_fee": "117.213009345",
        #             "bid_fee": "117.213009345",
        #             "created_at": "2018-04-05T14:09:15+09:00",
        #             "side": "bid",
        #         }
        #
        id = self.safe_string_2(trade, 'sequential_id', 'uuid')
        orderId = None
        timestamp = self.safe_integer(trade, 'timestamp')
        if timestamp is None:
            timestamp = self.parse8601(self.safe_string(trade, 'created_at'))
        side = None
        askOrBid = self.safe_string_lower_2(trade, 'ask_bid', 'side')
        if askOrBid == 'ask':
            side = 'sell'
        elif askOrBid == 'bid':
            side = 'buy'
        cost = self.safe_string(trade, 'funds')
        price = self.safe_string_2(trade, 'trade_price', 'price')
        amount = self.safe_string_2(trade, 'trade_volume', 'volume')
        marketId = self.safe_string_2(trade, 'market', 'code')
        market = self.safe_market(marketId, market, '-')
        fee = None
        feeCost = self.safe_string(trade, askOrBid + '_fee')
        if feeCost is not None:
            fee = {
                'currency': market['quote'],
                'cost': feeCost,
            }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }, market)

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        :see: https://docs.upbit.com/reference/%EC%B5%9C%EA%B7%BC-%EC%B2%B4%EA%B2%B0-%EB%82%B4%EC%97%AD
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        if limit is None:
            limit = 200
        request: dict = {
            'market': market['id'],
            'count': limit,
        }
        response = self.publicGetTradesTicks(self.extend(request, params))
        #
        #     [{            market: "BTC-ETH",
        #             "trade_date_utc": "2018-11-22",
        #             "trade_time_utc": "13:55:24",
        #                  "timestamp":  1542894924397,
        #                "trade_price":  0.02914289,
        #               "trade_volume":  0.20074397,
        #         "prev_closing_price":  0.02966,
        #               "change_price":  -0.00051711,
        #                    "ask_bid": "ASK",
        #              "sequential_id":  15428949259430000},
        #       {            market: "BTC-ETH",
        #             "trade_date_utc": "2018-11-22",
        #             "trade_time_utc": "13:03:10",
        #                  "timestamp":  1542891790123,
        #                "trade_price":  0.02917,
        #               "trade_volume":  7.392,
        #         "prev_closing_price":  0.02966,
        #               "change_price":  -0.00049,
        #                    "ask_bid": "ASK",
        #              "sequential_id":  15428917910540000}  ]
        #
        return self.parse_trades(response, market, since, limit)

    def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
        """
        :see: https://docs.upbit.com/reference/%EC%A3%BC%EB%AC%B8-%EA%B0%80%EB%8A%A5-%EC%A0%95%EB%B3%B4
        fetch the trading fees for a market
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `fee structure <https://docs.ccxt_zagrava.com/#/?id=fee-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'market': market['id'],
        }
        response = self.privateGetOrdersChance(self.extend(request, params))
        #
        #     {
        #         "bid_fee": "0.0005",
        #         "ask_fee": "0.0005",
        #         "maker_bid_fee": "0.0005",
        #         "maker_ask_fee": "0.0005",
        #         "market": {
        #             "id": "KRW-BTC",
        #             "name": "BTC/KRW",
        #             "order_types": ["limit"],
        #             "order_sides": ["ask", "bid"],
        #             "bid": {"currency": "KRW", "price_unit": null, "min_total": 5000},
        #             "ask": {"currency": "BTC", "price_unit": null, "min_total": 5000},
        #             "max_total": "1000000000.0",
        #             "state": "active"
        #         },
        #         "bid_account": {
        #             "currency": "KRW",
        #             "balance": "0.34202414",
        #             "locked": "4999.99999922",
        #             "avg_buy_price": "0",
        #             "avg_buy_price_modified": True,
        #             "unit_currency": "KRW"
        #         },
        #         "ask_account": {
        #             "currency": "BTC",
        #             "balance": "0.00048",
        #             "locked": "0.0",
        #             "avg_buy_price": "20870000",
        #             "avg_buy_price_modified": False,
        #             "unit_currency": "KRW"
        #         }
        #     }
        #
        askFee = self.safe_string(response, 'ask_fee')
        bidFee = self.safe_string(response, 'bid_fee')
        taker = Precise.string_max(askFee, bidFee)
        makerAskFee = self.safe_string(response, 'maker_ask_fee')
        makerBidFee = self.safe_string(response, 'maker_bid_fee')
        maker = Precise.string_max(makerAskFee, makerBidFee)
        return {
            'info': response,
            'symbol': symbol,
            'maker': self.parse_number(maker),
            'taker': self.parse_number(taker),
            'percentage': True,
            'tierBased': False,
        }

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     {
        #         "market": "BTC-ETH",
        #         "candle_date_time_utc": "2018-11-22T13:47:00",
        #         "candle_date_time_kst": "2018-11-22T22:47:00",
        #         "opening_price": 0.02915963,
        #         "high_price": 0.02915963,
        #         "low_price": 0.02915448,
        #         "trade_price": 0.02915448,
        #         "timestamp": 1542894473674,
        #         "candle_acc_trade_price": 0.0981629437535248,
        #         "candle_acc_trade_volume": 3.36693173,
        #         "unit": 1
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'candle_date_time_utc')),
            self.safe_number(ohlcv, 'opening_price'),
            self.safe_number(ohlcv, 'high_price'),
            self.safe_number(ohlcv, 'low_price'),
            self.safe_number(ohlcv, 'trade_price'),
            self.safe_number(ohlcv, 'candle_acc_trade_volume'),  # base volume
        ]

    def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        :see: https://docs.upbit.com/reference/%EB%B6%84minute-%EC%BA%94%EB%93%A4-1
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        timeframePeriod = self.parse_timeframe(timeframe)
        timeframeValue = self.safe_string(self.timeframes, timeframe, timeframe)
        if limit is None:
            limit = 200
        request: dict = {
            'market': market['id'],
            'timeframe': timeframeValue,
            'count': limit,
        }
        response = None
        if since is not None:
            # convert `since` to `to` value
            request['to'] = self.iso8601(self.sum(since, timeframePeriod * limit * 1000))
        if timeframeValue == 'minutes':
            numMinutes = int(round(timeframePeriod / 60))
            request['unit'] = numMinutes
            response = self.publicGetCandlesTimeframeUnit(self.extend(request, params))
        else:
            response = self.publicGetCandlesTimeframe(self.extend(request, params))
        #
        #     [
        #         {
        #             "market": "BTC-ETH",
        #             "candle_date_time_utc": "2018-11-22T13:47:00",
        #             "candle_date_time_kst": "2018-11-22T22:47:00",
        #             "opening_price": 0.02915963,
        #             "high_price": 0.02915963,
        #             "low_price": 0.02915448,
        #             "trade_price": 0.02915448,
        #             "timestamp": 1542894473674,
        #             "candle_acc_trade_price": 0.0981629437535248,
        #             "candle_acc_trade_volume": 3.36693173,
        #             "unit": 1
        #         },
        #         {
        #             "market": "BTC-ETH",
        #             "candle_date_time_utc": "2018-11-22T10:06:00",
        #             "candle_date_time_kst": "2018-11-22T19:06:00",
        #             "opening_price": 0.0294,
        #             "high_price": 0.02940882,
        #             "low_price": 0.02934283,
        #             "trade_price": 0.02937354,
        #             "timestamp": 1542881219276,
        #             "candle_acc_trade_price": 0.0762597110943884,
        #             "candle_acc_trade_volume": 2.5949617,
        #             "unit": 1
        #         }
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://docs.upbit.com/reference/%EC%A3%BC%EB%AC%B8%ED%95%98%EA%B8%B0
        :see: https://global-docs.upbit.com/reference/order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much you want to trade in units of the base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.cost]: for market buy orders, the quote quantity that can be used alternative for the amount
        :param str [params.timeInForce]: 'IOC' or 'FOK'
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        orderSide = None
        if side == 'buy':
            orderSide = 'bid'
        elif side == 'sell':
            orderSide = 'ask'
        else:
            raise InvalidOrder(self.id + ' createOrder() allows buy or sell side only!')
        request: dict = {
            'market': market['id'],
            'side': orderSide,
        }
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
        if (type == 'market') and (side == 'buy'):
            # for market buy it requires the amount of quote currency to spend
            quoteAmount = None
            createMarketBuyOrderRequiresPrice = True
            createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
            cost = self.safe_number(params, 'cost')
            params = self.omit(params, 'cost')
            if cost is not None:
                quoteAmount = self.cost_to_precision(symbol, cost)
            elif createMarketBuyOrderRequiresPrice:
                if price is None:
                    raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
                else:
                    amountString = self.number_to_string(amount)
                    priceString = self.number_to_string(price)
                    costRequest = Precise.string_mul(amountString, priceString)
                    quoteAmount = self.cost_to_precision(symbol, costRequest)
            else:
                quoteAmount = self.cost_to_precision(symbol, amount)
            request['ord_type'] = 'price'
            request['price'] = quoteAmount
        else:
            request['ord_type'] = type
            request['volume'] = self.amount_to_precision(symbol, amount)
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'identifier')
        if clientOrderId is not None:
            request['identifier'] = clientOrderId
        if type != 'market':
            timeInForce = self.safe_string_lower_2(params, 'timeInForce', 'time_in_force')
            params = self.omit(params, 'timeInForce')
            if timeInForce is not None:
                request['time_in_force'] = timeInForce
        params = self.omit(params, ['clientOrderId', 'identifier'])
        response = self.privatePostOrders(self.extend(request, params))
        #
        #     {
        #         "uuid": "cdd92199-2897-4e14-9448-f923320408ad",
        #         "side": "bid",
        #         "ord_type": "limit",
        #         "price": "100.0",
        #         "avg_price": "0.0",
        #         "state": "wait",
        #         "market": "KRW-BTC",
        #         "created_at": "2018-04-10T15:42:23+09:00",
        #         "volume": "0.01",
        #         "remaining_volume": "0.01",
        #         "reserved_fee": "0.0015",
        #         "remaining_fee": "0.0015",
        #         "paid_fee": "0.0",
        #         "locked": "1.0015",
        #         "executed_volume": "0.0",
        #         "trades_count": 0
        #     }
        #
        return self.parse_order(response)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://docs.upbit.com/reference/%EC%A3%BC%EB%AC%B8-%EC%B7%A8%EC%86%8C
        cancels an open order
        :param str id: order id
        :param str symbol: not used by upbit cancelOrder()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            'uuid': id,
        }
        response = self.privateDeleteOrder(self.extend(request, params))
        #
        #     {
        #         "uuid": "cdd92199-2897-4e14-9448-f923320408ad",
        #         "side": "bid",
        #         "ord_type": "limit",
        #         "price": "100.0",
        #         "state": "wait",
        #         "market": "KRW-BTC",
        #         "created_at": "2018-04-10T15:42:23+09:00",
        #         "volume": "0.01",
        #         "remaining_volume": "0.01",
        #         "reserved_fee": "0.0015",
        #         "remaining_fee": "0.0015",
        #         "paid_fee": "0.0",
        #         "locked": "1.0015",
        #         "executed_volume": "0.0",
        #         "trades_count": 0
        #     }
        #
        return self.parse_order(response)

    def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        :see: https://docs.upbit.com/reference/%EC%9E%85%EA%B8%88-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%A1%B0%ED%9A%8C
        fetch all deposits made to an account
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        request: dict = {
            # 'page': 1,
            # 'order_by': 'asc',  # 'desc'
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if limit is not None:
            request['limit'] = limit  # default is 100
        response = self.privateGetDeposits(self.extend(request, params))
        #
        #     [
        #         {
        #             "type": "deposit",
        #             "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830",
        #             "currency": "KRW",
        #             "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a",
        #             "state": "ACCEPTED",
        #             "created_at": "2017-12-08T15:38:02+09:00",
        #             "done_at": "2017-12-08T15:38:02+09:00",
        #             "amount": "100000.0",
        #             "fee": "0.0"
        #         },
        #         ...,
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit)

    def fetch_deposit(self, id: str, code: Str = None, params={}):
        """
        fetch information on a deposit
        :see: https://global-docs.upbit.com/reference/individual-deposit-inquiry
        :param str id: the unique id for the deposit
        :param str [code]: unified currency code of the currency deposited
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.txid]: withdrawal transaction id, the id argument is reserved for uuid
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        request: dict = {
            'uuid': id,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = self.privateGetDeposit(self.extend(request, params))
        #
        #     {
        #         "type": "deposit",
        #         "uuid": "7f54527e-2eee-4268-860e-fd8b9d7fe3c7",
        #         "currency": "ADA",
        #         "net_type": "ADA",
        #         "txid": "99795bbfeca91eaa071068bb659b33eeb65d8aaff2551fdf7c78f345d188952b",
        #         "state": "ACCEPTED",
        #         "created_at": "2023-12-12T04:58:41Z",
        #         "done_at": "2023-12-12T05:31:50Z",
        #         "amount": "35.72344",
        #         "fee": "0.0",
        #         "transaction_type": "default"
        #     }
        #
        return self.parse_transaction(response, currency)

    def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        :see: https://docs.upbit.com/reference/%EC%A0%84%EC%B2%B4-%EC%B6%9C%EA%B8%88-%EC%A1%B0%ED%9A%8C
        fetch all withdrawals made from an account
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        request: dict = {
            # 'state': 'submitting',  # 'submitted', 'almost_accepted', 'rejected', 'accepted', 'processing', 'done', 'canceled'
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if limit is not None:
            request['limit'] = limit  # default is 100
        response = self.privateGetWithdraws(self.extend(request, params))
        #
        #     [
        #         {
        #             "type": "withdraw",
        #             "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79",
        #             "currency": "BTC",
        #             "txid": null,
        #             "state": "processing",
        #             "created_at": "2018-04-13T11:24:01+09:00",
        #             "done_at": null,
        #             "amount": "0.01",
        #             "fee": "0.0",
        #             "krw_amount": "80420.0"
        #         },
        #         ...,
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit)

    def fetch_withdrawal(self, id: str, code: Str = None, params={}):
        """
        fetch data on a currency withdrawal via the withdrawal id
        :see: https://global-docs.upbit.com/reference/individual-withdrawal-inquiry
        :param str id: the unique id for the withdrawal
        :param str [code]: unified currency code of the currency withdrawn
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.txid]: withdrawal transaction id, the id argument is reserved for uuid
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        request: dict = {
            'uuid': id,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        response = self.privateGetWithdraw(self.extend(request, params))
        #
        #     {
        #         "type": "withdraw",
        #         "uuid": "95ef274b-23a6-4de4-95b0-5cbef4ca658f",
        #         "currency": "ADA",
        #         "net_type": "ADA",
        #         "txid": "b1528f149297a71671b86636f731f8fdb0ff53da0f1d8c19093d59df96f34583",
        #         "state": "DONE",
        #         "created_at": "2023-12-14T02:46:52Z",
        #         "done_at": "2023-12-14T03:10:11Z",
        #         "amount": "35.22344",
        #         "fee": "0.5",
        #         "transaction_type": "default"
        #     }
        #
        return self.parse_transaction(response, currency)

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

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # fetchDeposits, fetchDeposit
        #
        #     {
        #         "type": "deposit",
        #         "uuid": "94332e99-3a87-4a35-ad98-28b0c969f830",
        #         "currency": "KRW",
        #         "txid": "9e37c537-6849-4c8b-a134-57313f5dfc5a",
        #         "state": "ACCEPTED",
        #         "created_at": "2017-12-08T15:38:02+09:00",
        #         "done_at": "2017-12-08T15:38:02+09:00",
        #         "amount": "100000.0",
        #         "fee": "0.0"
        #     }
        #
        # fetchWithdrawals, fetchWithdrawal
        #
        #     {
        #         "type": "withdraw",
        #         "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79",
        #         "currency": "BTC",
        #         "txid": "cd81e9b45df8da29f936836e58c907a106057e454a45767a7b06fcb19b966bba",
        #         "state": "processing",
        #         "created_at": "2018-04-13T11:24:01+09:00",
        #         "done_at": null,
        #         "amount": "0.01",
        #         "fee": "0.0",
        #         "krw_amount": "80420.0"
        #     }
        #
        address = None  # not present in the data structure received from the exchange
        tag = None  # not present in the data structure received from the exchange
        updatedRaw = self.safe_string(transaction, 'done_at')
        timestamp = self.parse8601(self.safe_string(transaction, 'created_at', updatedRaw))
        type = self.safe_string(transaction, 'type')
        if type == 'withdraw':
            type = 'withdrawal'
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        return {
            'info': transaction,
            'id': self.safe_string(transaction, 'uuid'),
            'currency': code,
            'amount': self.safe_number(transaction, 'amount'),
            'network': None,
            'address': address,
            'addressTo': None,
            'addressFrom': None,
            'tag': tag,
            'tagTo': None,
            'tagFrom': None,
            'status': self.parse_transaction_status(self.safe_string_lower(transaction, 'state')),
            'type': type,
            'updated': self.parse8601(updatedRaw),
            'txid': self.safe_string(transaction, 'txid'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'internal': None,
            'comment': None,
            'fee': {
                'currency': code,
                'cost': self.safe_number(transaction, 'fee'),
            },
        }

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

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        #     {
        #         "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",
        #             },
        #         ],
        #     }
        #
        # fetchOpenOrders, fetchClosedOrders, fetchCanceledOrders
        #
        #     {
        #         "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
        #         "side": "ask",
        #         "ord_type": "limit",
        #         "price": "1.5",
        #         "state": "wait",
        #         "market": "SGD-XRP",
        #         "created_at": "2024-06-05T09:37:10Z",
        #         "volume": "10",
        #         "remaining_volume": "10",
        #         "reserved_fee": "0",
        #         "remaining_fee": "0",
        #         "paid_fee": "0",
        #         "locked": "10",
        #         "executed_volume": "0",
        #         "executed_funds": "0",
        #         "trades_count": 0,
        #         "time_in_force": "ioc"
        #     }
        #
        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_string(order, 'price')
        amount = self.safe_string(order, 'volume')
        remaining = self.safe_string(order, 'remaining_volume')
        filled = self.safe_string(order, 'executed_volume')
        cost = None
        if type == 'price':
            type = 'market'
            cost = price
            price = None
        average = None
        fee = None
        feeCost = self.safe_string(order, 'paid_fee')
        marketId = self.safe_string(order, 'market')
        market = self.safe_market(marketId, market)
        trades = self.safe_value(order, 'trades', [])
        trades = self.parse_trades(trades, market, None, None, {
            'order': id,
            'type': type,
        })
        numTrades = len(trades)
        if numTrades > 0:
            # the timestamp in fetchOrder trades is missing
            lastTradeTimestamp = trades[numTrades - 1]['timestamp']
            getFeesFromTrades = False
            if feeCost is None:
                getFeesFromTrades = True
                feeCost = '0'
            cost = '0'
            for i in range(0, numTrades):
                trade = trades[i]
                cost = Precise.string_add(cost, self.safe_string(trade, 'cost'))
                if getFeesFromTrades:
                    tradeFee = self.safe_value(trades[i], 'fee', {})
                    tradeFeeCost = self.safe_string(tradeFee, 'cost')
                    if tradeFeeCost is not None:
                        feeCost = Precise.string_add(feeCost, tradeFeeCost)
            average = Precise.string_div(cost, filled)
        if feeCost is not None:
            fee = {
                'currency': market['quote'],
                'cost': feeCost,
            }
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': market['symbol'],
            'type': type,
            'timeInForce': self.safe_string_upper(order, 'time_in_force'),
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'cost': self.parse_number(cost),
            'average': self.parse_number(average),
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': trades,
        })

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://global-docs.upbit.com/reference/open-order
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of open order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.state]: default is 'wait', set to 'watch' for stop limit orders
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market'] = market['id']
        if limit is not None:
            request['limit'] = limit
        response = self.privateGetOrdersOpen(self.extend(request, params))
        #
        #     [
        #         {
        #             "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
        #             "side": "ask",
        #             "ord_type": "limit",
        #             "price": "1.5",
        #             "state": "wait",
        #             "market": "SGD-XRP",
        #             "created_at": "2024-06-05T09:37:10Z",
        #             "volume": "10",
        #             "remaining_volume": "10",
        #             "reserved_fee": "0",
        #             "remaining_fee": "0",
        #             "paid_fee": "0",
        #             "locked": "10",
        #             "executed_volume": "0",
        #             "executed_funds": "0",
        #             "trades_count": 0
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user
        :see: https://global-docs.upbit.com/reference/closed-order
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest order
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            'state': 'done',
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market'] = market['id']
        if since is not None:
            request['start_time'] = since
        if limit is not None:
            request['limit'] = limit
        request, params = self.handle_until_option('end_time', request, params)
        response = self.privateGetOrdersClosed(self.extend(request, params))
        #
        #     [
        #         {
        #             "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
        #             "side": "ask",
        #             "ord_type": "limit",
        #             "price": "1.5",
        #             "state": "done",
        #             "market": "SGD-XRP",
        #             "created_at": "2024-06-05T09:37:10Z",
        #             "volume": "10",
        #             "remaining_volume": "10",
        #             "reserved_fee": "0",
        #             "remaining_fee": "0",
        #             "paid_fee": "0",
        #             "locked": "10",
        #             "executed_volume": "0",
        #             "executed_funds": "0",
        #             "trades_count": 0,
        #             "time_in_force": "ioc"
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetches information on multiple canceled orders made by the user
        :see: https://global-docs.upbit.com/reference/closed-order
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: timestamp in ms of the earliest order, default is None
        :param int [limit]: max number of orders to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest order
        :returns dict: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            'state': 'cancel',
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market'] = market['id']
        if since is not None:
            request['start_time'] = since
        if limit is not None:
            request['limit'] = limit
        request, params = self.handle_until_option('end_time', request, params)
        response = self.privateGetOrdersClosed(self.extend(request, params))
        #
        #     [
        #         {
        #             "uuid": "637fd66-d019-4d77-bee6-8e0cff28edd9",
        #             "side": "ask",
        #             "ord_type": "limit",
        #             "price": "1.5",
        #             "state": "cancel",
        #             "market": "SGD-XRP",
        #             "created_at": "2024-06-05T09:37:10Z",
        #             "volume": "10",
        #             "remaining_volume": "10",
        #             "reserved_fee": "0",
        #             "remaining_fee": "0",
        #             "paid_fee": "0",
        #             "locked": "10",
        #             "executed_volume": "0",
        #             "executed_funds": "0",
        #             "trades_count": 0,
        #             "time_in_force": "ioc"
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://docs.upbit.com/reference/%EA%B0%9C%EB%B3%84-%EC%A3%BC%EB%AC%B8-%EC%A1%B0%ED%9A%8C
        fetches information on an order made by the user
        :param str symbol: not used by upbit fetchOrder
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        request: dict = {
            'uuid': id,
        }
        response = self.privateGetOrder(self.extend(request, params))
        #
        #     {
        #         "uuid": "a08f09b1-1718-42e2-9358-f0e5e083d3ee",
        #         "side": "bid",
        #         "ord_type": "limit",
        #         "price": "17417000.0",
        #         "state": "done",
        #         "market": "KRW-BTC",
        #         "created_at": "2018-04-05T14:09:14+09:00",
        #         "volume": "1.0",
        #         "remaining_volume": "0.0",
        #         "reserved_fee": "26125.5",
        #         "remaining_fee": "25974.0",
        #         "paid_fee": "151.5",
        #         "locked": "17341974.0",
        #         "executed_volume": "1.0",
        #         "trades_count": 2,
        #         "trades": [
        #             {
        #                 "market": "KRW-BTC",
        #                 "uuid": "78162304-1a4d-4524-b9e6-c9a9e14d76c3",
        #                 "price": "101000.0",
        #                 "volume": "0.77368323",
        #                 "funds": "78142.00623",
        #                 "ask_fee": "117.213009345",
        #                 "bid_fee": "117.213009345",
        #                 "created_at": "2018-04-05T14:09:15+09:00",
        #                 "side": "bid"
        #             },
        #             {
        #                 "market": "KRW-BTC",
        #                 "uuid": "f73da467-c42f-407d-92fa-e10d86450a20",
        #                 "price": "101000.0",
        #                 "volume": "0.22631677",
        #                 "funds": "22857.99377",
        #                 "ask_fee": "34.286990655",
        #                 "bid_fee": "34.286990655",
        #                 "created_at": "2018-04-05T14:09:15+09:00",
        #                 "side": "bid"
        #             }
        #         ]
        #     }
        #
        return self.parse_order(response)

    def fetch_deposit_addresses(self, codes: Strings = None, params={}):
        """
        :see: https://docs.upbit.com/reference/%EC%A0%84%EC%B2%B4-%EC%9E%85%EA%B8%88-%EC%A3%BC%EC%86%8C-%EC%A1%B0%ED%9A%8C
        fetch deposit addresses for multiple currencies and chain types
        :param str[]|None codes: list of unified currency codes, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `address structures <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        self.load_markets()
        response = self.privateGetDepositsCoinAddresses(params)
        #
        #     [
        #         {
        #             "currency": "BTC",
        #             "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD",
        #             "secondary_address": null
        #         },
        #         {
        #             "currency": "ETH",
        #             "deposit_address": "0x0d73e0a482b8cf568976d2e8688f4a899d29301c",
        #             "secondary_address": null
        #         },
        #         {
        #             "currency": "XRP",
        #             "deposit_address": "rN9qNpgnBaZwqCg8CvUZRPqCcPPY7wfWep",
        #             "secondary_address": "3057887915"
        #         }
        #     ]
        #
        return self.parse_deposit_addresses(response, codes)

    def parse_deposit_address(self, depositAddress, currency: Currency = None):
        #
        #    {
        #        currency: 'XRP',
        #        net_type: 'XRP',
        #        deposit_address: 'raQwCVAJVqjrVm1Nj5SFRcX8i22BhdC9WA',
        #        secondary_address: '167029435'
        #    }
        #
        address = self.safe_string(depositAddress, 'deposit_address')
        tag = self.safe_string(depositAddress, 'secondary_address')
        currencyId = self.safe_string(depositAddress, 'currency')
        code = self.safe_currency_code(currencyId)
        networkId = self.safe_string(depositAddress, 'net_type')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': self.network_id_to_code(networkId),
            'info': depositAddress,
        }

    def fetch_deposit_address(self, code: str, params={}):
        """
        :see: https://docs.upbit.com/reference/%EC%A0%84%EC%B2%B4-%EC%9E%85%EA%B8%88-%EC%A3%BC%EC%86%8C-%EC%A1%B0%ED%9A%8C
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str params['network']: deposit chain, can view all chains via self.publicGetWalletAssets, default is eth, unless the currency has a default chain within self.options['networks']
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        networkCode = None
        networkCode, params = self.handle_network_code_and_params(params)
        if networkCode is None:
            raise ArgumentsRequired(self.id + ' fetchDepositAddress requires params["network"]')
        response = self.privateGetDepositsCoinAddress(self.extend({
            'currency': currency['id'],
            'net_type': self.network_code_to_id(networkCode, currency['code']),
        }, params))
        #
        #    {
        #        currency: 'XRP',
        #        net_type: 'XRP',
        #        deposit_address: 'raQwCVAJVqjrVm1Nj5SFRcX8i22BhdC9WA',
        #        secondary_address: '167029435'
        #    }
        #
        return self.parse_deposit_address(response)

    def create_deposit_address(self, code: str, params={}):
        """
        :see: https://docs.upbit.com/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
        create a currency deposit address
        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/6452
        response = self.privatePostDepositsGenerateCoinAddress(self.extend(request, params))
        #
        # https://docs.upbit.com/v1.0/reference#%EC%9E%85%EA%B8%88-%EC%A3%BC%EC%86%8C-%EC%83%9D%EC%84%B1-%EC%9A%94%EC%B2%AD
        # can be any of the two responses:
        #
        #     {
        #         "success" : True,
        #         "message" : "Creating BTC deposit address."
        #     }
        #
        #     {
        #         "currency": "BTC",
        #         "deposit_address": "3EusRwybuZUhVDeHL7gh3HSLmbhLcy7NqD",
        #         "secondary_address": null
        #     }
        #
        message = self.safe_string(response, 'message')
        if message is not None:
            raise AddressPending(self.id + ' is generating ' + code + ' deposit address, call fetchDepositAddress or createDepositAddress one more time later to retrieve the generated address')
        return self.parse_deposit_address(response)

    def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        :see: https://docs.upbit.com/reference/디지털자산-출금하기
        :see: https://docs.upbit.com/reference/%EC%9B%90%ED%99%94-%EC%B6%9C%EA%B8%88%ED%95%98%EA%B8%B0
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'amount': amount,
        }
        response = None
        if code != 'KRW':
            self.check_address(address)
            # 2023-05-23 Change to required parameters for digital assets
            network = self.safe_string_upper_2(params, 'network', 'net_type')
            if network is None:
                raise ArgumentsRequired(self.id + ' withdraw() requires a network argument')
            params = self.omit(params, ['network'])
            request['net_type'] = network
            request['currency'] = currency['id']
            request['address'] = address
            if tag is not None:
                request['secondary_address'] = tag
            params = self.omit(params, 'network')
            response = self.privatePostWithdrawsCoin(self.extend(request, params))
        else:
            response = self.privatePostWithdrawsKrw(self.extend(request, params))
        #
        #     {
        #         "type": "withdraw",
        #         "uuid": "9f432943-54e0-40b7-825f-b6fec8b42b79",
        #         "currency": "BTC",
        #         "txid": "ebe6937b-130e-4066-8ac6-4b0e67f28adc",
        #         "state": "processing",
        #         "created_at": "2018-04-13T11:24:01+09:00",
        #         "done_at": null,
        #         "amount": "0.01",
        #         "fee": "0.0",
        #         "krw_amount": "80420.0"
        #     }
        #
        return self.parse_transaction(response)

    def nonce(self):
        return self.milliseconds()

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.implode_params(self.urls['api'][api], {
            'hostname': self.hostname,
        })
        url += '/' + self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if method != 'POST':
            if query:
                url += '?' + self.urlencode(query)
        if api == 'private':
            self.check_required_credentials()
            headers = {}
            nonce = self.uuid()
            request: dict = {
                'access_key': self.apiKey,
                'nonce': nonce,
            }
            hasQuery = query
            auth = None
            if (method != 'GET') and (method != 'DELETE'):
                body = self.json(params)
                headers['Content-Type'] = 'application/json'
            if hasQuery:
                auth = self.urlencode(query)
            if auth is not None:
                hash = self.hash(self.encode(auth), 'sha512')
                request['query_hash'] = hash
                request['query_hash_alg'] = 'SHA512'
            token = self.jwt(request, self.encode(self.secret), 'sha256')
            headers['Authorization'] = 'Bearer ' + token
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None  # fallback to default error handler
        #
        #   {'error': {'message': "Missing request parameter error. Check the required parameters!", 'name': 400}},
        #   {'error': {'message': "side is missing, side does not have a valid value", 'name': "validation_error"}},
        #   {'error': {'message': "개인정보 제 3자 제공 동의가 필요합니다.", 'name': "thirdparty_agreement_required"}},
        #   {'error': {'message': "권한이 부족합니다.", 'name': "out_of_scope"}},
        #   {'error': {'message': "주문을 찾지 못했습니다.", 'name': "order_not_found"}},
        #   {'error': {'message': "주문가능한 금액(ETH)이 부족합니다.", 'name': "insufficient_funds_ask"}},
        #   {'error': {'message': "주문가능한 금액(BTC)이 부족합니다.", 'name': "insufficient_funds_bid"}},
        #   {'error': {'message': "잘못된 엑세스 키입니다.", 'name': "invalid_access_key"}},
        #   {'error': {'message': "Jwt 토큰 검증에 실패했습니다.", 'name': "jwt_verification"}}
        #
        error = self.safe_value(response, 'error')
        if error is not None:
            message = self.safe_string(error, 'message')
            name = self.safe_string(error, 'name')
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], name, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], name, feedback)
            raise ExchangeError(feedback)  # unknown message
        return None
