# -*- 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.async_support.base.exchange import Exchange
from ccxt_zagrava.abstract.bithumb import ImplicitAPI
import hashlib
from ccxt_zagrava.base.types import Balances, Currency, Int, Market, MarketInterface, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, 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 InvalidAddress
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import ExchangeNotAvailable
from ccxt_zagrava.base.decimal_to_precision import TRUNCATE
from ccxt_zagrava.base.decimal_to_precision import DECIMAL_PLACES
from ccxt_zagrava.base.decimal_to_precision import SIGNIFICANT_DIGITS
from ccxt_zagrava.base.precise import Precise


class bithumb(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(bithumb, self).describe(), {
            'id': 'bithumb',
            'name': 'Bithumb',
            'countries': ['KR'],  # South Korea
            'rateLimit': 500,
            'pro': True,
            'has': {
                'CORS': True,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelOrder': True,
                'closeAllPositions': False,
                'closePosition': False,
                'createMarketOrder': True,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'fetchBalance': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLeverage': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'reduceMargin': False,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': False,
                'withdraw': True,
            },
            'hostname': 'bithumb.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/30597177-ea800172-9d5e-11e7-804c-b9d4fa9b56b0.jpg',
                'api': {
                    'public': 'https://api.{hostname}/public',
                    'private': 'https://api.{hostname}',
                },
                'www': 'https://www.bithumb.com',
                'doc': 'https://apidocs.bithumb.com',
                'fees': 'https://en.bithumb.com/customer_support/info_fee',
            },
            'api': {
                'public': {
                    'get': [
                        'ticker/ALL_{quoteId}',
                        'ticker/{baseId}_{quoteId}',
                        'orderbook/ALL_{quoteId}',
                        'orderbook/{baseId}_{quoteId}',
                        'transaction_history/{baseId}_{quoteId}',
                        'network-info',
                        'assetsstatus/multichain/ALL',
                        'assetsstatus/multichain/{currency}',
                        'withdraw/minimum/ALL',
                        'withdraw/minimum/{currency}',
                        'assetsstatus/ALL',
                        'assetsstatus/{baseId}',
                        'candlestick/{baseId}_{quoteId}/{interval}',
                    ],
                },
                'private': {
                    'post': [
                        'info/account',
                        'info/balance',
                        'info/wallet_address',
                        'info/ticker',
                        'info/orders',
                        'info/user_transactions',
                        'info/order_detail',
                        'trade/place',
                        'trade/cancel',
                        'trade/btc_withdrawal',
                        'trade/krw_deposit',
                        'trade/krw_withdrawal',
                        'trade/market_buy',
                        'trade/market_sell',
                        'trade/stop_limit',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': self.parse_number('0.0025'),
                    'taker': self.parse_number('0.0025'),
                },
            },
            'precisionMode': SIGNIFICANT_DIGITS,
            'exceptions': {
                'Bad Request(SSL)': BadRequest,
                'Bad Request(Bad Method)': BadRequest,
                'Bad Request.(Auth Data)': AuthenticationError,  # {"status": "5100", "message": "Bad Request.(Auth Data)"}
                'Not Member': AuthenticationError,
                'Invalid Apikey': AuthenticationError,  # {"status":"5300","message":"Invalid Apikey"}
                'Method Not Allowed.(Access IP)': PermissionDenied,
                'Method Not Allowed.(BTC Adress)': InvalidAddress,
                'Method Not Allowed.(Access)': PermissionDenied,
                'Database Fail': ExchangeNotAvailable,
                'Invalid Parameter': BadRequest,
                '5600': ExchangeError,
                'Unknown Error': ExchangeError,
                'After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions': ExchangeError,  # {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"}
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '10m': '10m',
                '30m': '30m',
                '1h': '1h',
                '6h': '6h',
                '12h': '12h',
                '1d': '24h',
            },
            'options': {
                'quoteCurrencies': {
                    'BTC': {
                        'limits': {
                            'cost': {
                                'min': 0.0002,
                                'max': 100,
                            },
                        },
                    },
                    'KRW': {
                        'limits': {
                            'cost': {
                                'min': 500,
                                'max': 5000000000,
                            },
                        },
                    },
                },
            },
            'commonCurrencies': {
                'ALT': 'ArchLoot',
                'FTC': 'FTC2',
                'SOC': 'Soda Coin',
            },
        })

    def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
        # bithumb has a different type of conflict in markets, because
        # their ids are the base currency(BTC for instance), so we can have
        # multiple "BTC" ids representing the different markets(BTC/ETH, "BTC/DOGE", etc)
        # since they're the same we just need to return one
        return super(bithumb, self).safe_market(marketId, market, delimiter, 'spot')

    def amount_to_precision(self, symbol, amount):
        return self.decimal_to_precision(amount, TRUNCATE, self.markets[symbol]['precision']['amount'], DECIMAL_PLACES)

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for bithumb
        :see: https://apidocs.bithumb.com/reference/%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C-all
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        result = []
        quoteCurrencies = self.safe_value(self.options, 'quoteCurrencies', {})
        quotes = list(quoteCurrencies.keys())
        for i in range(0, len(quotes)):
            quote = quotes[i]
            quoteId = quote
            extension = self.safe_value(quoteCurrencies, quote, {})
            request: dict = {
                'quoteId': quoteId,
            }
            response = await self.publicGetTickerALLQuoteId(self.extend(request, params))
            data = self.safe_value(response, 'data')
            currencyIds = list(data.keys())
            for j in range(0, len(currencyIds)):
                currencyId = currencyIds[j]
                if currencyId == 'date':
                    continue
                market = data[currencyId]
                base = self.safe_currency_code(currencyId)
                active = True
                if isinstance(market, list):
                    numElements = len(market)
                    if numElements == 0:
                        active = False
                entry = self.deep_extend({
                    'id': currencyId,
                    'symbol': base + '/' + quote,
                    'base': base,
                    'quote': quote,
                    'settle': None,
                    'baseId': currencyId,
                    'quoteId': quoteId,
                    'settleId': None,
                    'type': 'spot',
                    'spot': True,
                    'margin': False,
                    'swap': False,
                    'future': False,
                    'option': False,
                    'active': active,
                    'contract': False,
                    'linear': None,
                    'inverse': None,
                    'contractSize': None,
                    'expiry': None,
                    'expiryDateTime': None,
                    'strike': None,
                    'optionType': None,
                    'precision': {
                        'amount': int('4'),
                        'price': int('4'),
                    },
                    'limits': {
                        'leverage': {
                            'min': None,
                            'max': None,
                        },
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                        'price': {
                            'min': None,
                            'max': None,
                        },
                        'cost': {},  # set via options
                    },
                    'created': None,
                    'info': market,
                }, extension)
                result.append(entry)
        return result

    def parse_balance(self, response) -> Balances:
        result: dict = {'info': response}
        balances = self.safe_value(response, 'data')
        codes = list(self.currencies.keys())
        for i in range(0, len(codes)):
            code = codes[i]
            account = self.account()
            currency = self.currency(code)
            lowerCurrencyId = self.safe_string_lower(currency, 'id')
            account['total'] = self.safe_string(balances, 'total_' + lowerCurrencyId)
            account['used'] = self.safe_string(balances, 'in_use_' + lowerCurrencyId)
            account['free'] = self.safe_string(balances, 'available_' + lowerCurrencyId)
            result[code] = account
        return self.safe_balance(result)

    async def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://apidocs.bithumb.com/reference/%EB%B3%B4%EC%9C%A0%EC%9E%90%EC%82%B0-%EC%A1%B0%ED%9A%8C
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        request: dict = {
            'currency': 'ALL',
        }
        response = await self.privatePostInfoBalance(self.extend(request, params))
        return self.parse_balance(response)

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://apidocs.bithumb.com/reference/%ED%98%B8%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'baseId': market['baseId'],
            'quoteId': market['quoteId'],
        }
        if limit is not None:
            request['count'] = limit  # default 30, max 30
        response = await self.publicGetOrderbookBaseIdQuoteId(self.extend(request, params))
        #
        #     {
        #         "status":"0000",
        #         "data":{
        #             "timestamp":"1587621553942",
        #             "payment_currency":"KRW",
        #             "order_currency":"BTC",
        #             "bids":[
        #                 {"price":"8652000","quantity":"0.0043"},
        #                 {"price":"8651000","quantity":"0.0049"},
        #                 {"price":"8650000","quantity":"8.4791"},
        #             ],
        #             "asks":[
        #                 {"price":"8654000","quantity":"0.119"},
        #                 {"price":"8655000","quantity":"0.254"},
        #                 {"price":"8658000","quantity":"0.119"},
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        timestamp = self.safe_integer(data, 'timestamp')
        return self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 'price', 'quantity')

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        # fetchTicker, fetchTickers
        #
        #     {
        #         "opening_price":"227100",
        #         "closing_price":"228400",
        #         "min_price":"222300",
        #         "max_price":"230000",
        #         "units_traded":"82618.56075337",
        #         "acc_trade_value":"18767376138.6031",
        #         "prev_closing_price":"227100",
        #         "units_traded_24H":"151871.13484676",
        #         "acc_trade_value_24H":"34247610416.8974",
        #         "fluctate_24H":"8700",
        #         "fluctate_rate_24H":"3.96",
        #         "date":"1587710327264",  # fetchTickers inject self
        #     }
        #
        timestamp = self.safe_integer(ticker, 'date')
        symbol = self.safe_symbol(None, market)
        open = self.safe_string(ticker, 'opening_price')
        close = self.safe_string(ticker, 'closing_price')
        baseVolume = self.safe_string(ticker, 'units_traded_24H')
        quoteVolume = self.safe_string(ticker, 'acc_trade_value_24H')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'max_price'),
            'low': self.safe_string(ticker, 'min_price'),
            'bid': self.safe_string(ticker, 'buy_price'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'sell_price'),
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': close,
            'last': close,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://apidocs.bithumb.com/reference/%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C-all
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        result: dict = {}
        quoteCurrencies = self.safe_value(self.options, 'quoteCurrencies', {})
        quotes = list(quoteCurrencies.keys())
        for i in range(0, len(quotes)):
            quote = quotes[i]
            quoteId = quote
            request: dict = {
                'quoteId': quoteId,
            }
            response = await self.publicGetTickerALLQuoteId(self.extend(request, params))
            #
            #     {
            #         "status":"0000",
            #         "data":{
            #             "BTC":{
            #                 "opening_price":"9045000",
            #                 "closing_price":"9132000",
            #                 "min_price":"8938000",
            #                 "max_price":"9168000",
            #                 "units_traded":"4619.79967497",
            #                 "acc_trade_value":"42021363832.5187",
            #                 "prev_closing_price":"9041000",
            #                 "units_traded_24H":"8793.5045804",
            #                 "acc_trade_value_24H":"78933458515.4962",
            #                 "fluctate_24H":"530000",
            #                 "fluctate_rate_24H":"6.16"
            #             },
            #             "date":"1587710878669"
            #         }
            #     }
            #
            data = self.safe_value(response, 'data', {})
            timestamp = self.safe_integer(data, 'date')
            tickers = self.omit(data, 'date')
            currencyIds = list(tickers.keys())
            for j in range(0, len(currencyIds)):
                currencyId = currencyIds[j]
                ticker = data[currencyId]
                base = self.safe_currency_code(currencyId)
                symbol = base + '/' + quote
                market = self.safe_market(symbol)
                ticker['date'] = timestamp
                result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://apidocs.bithumb.com/reference/%ED%98%84%EC%9E%AC%EA%B0%80-%EC%A0%95%EB%B3%B4-%EC%A1%B0%ED%9A%8C
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'baseId': market['baseId'],
            'quoteId': market['quoteId'],
        }
        response = await self.publicGetTickerBaseIdQuoteId(self.extend(request, params))
        #
        #     {
        #         "status":"0000",
        #         "data":{
        #             "opening_price":"227100",
        #             "closing_price":"228400",
        #             "min_price":"222300",
        #             "max_price":"230000",
        #             "units_traded":"82618.56075337",
        #             "acc_trade_value":"18767376138.6031",
        #             "prev_closing_price":"227100",
        #             "units_traded_24H":"151871.13484676",
        #             "acc_trade_value_24H":"34247610416.8974",
        #             "fluctate_24H":"8700",
        #             "fluctate_rate_24H":"3.96",
        #             "date":"1587710327264"
        #         }
        #     }
        #
        data = self.safe_dict(response, 'data', {})
        return self.parse_ticker(data, market)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     [
        #         1576823400000,  # 기준 시간
        #         "8284000",  # 시가
        #         "8286000",  # 종가
        #         "8289000",  # 고가
        #         "8276000",  # 저가
        #         "15.41503692"  # 거래량
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://apidocs.bithumb.com/reference/candlestick-rest-api
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'baseId': market['baseId'],
            'quoteId': market['quoteId'],
            'interval': self.safe_string(self.timeframes, timeframe, timeframe),
        }
        response = await self.publicGetCandlestickBaseIdQuoteIdInterval(self.extend(request, params))
        #
        #     {
        #         "status": "0000",
        #         "data": {
        #             [
        #                 1576823400000,  # 기준 시간
        #                 "8284000",  # 시가
        #                 "8286000",  # 종가
        #                 "8289000",  # 고가
        #                 "8276000",  # 저가
        #                 "15.41503692"  # 거래량
        #             ],
        #             [
        #                 1576824000000,  # 기준 시간
        #                 "8284000",  # 시가
        #                 "8281000",  # 종가
        #                 "8289000",  # 고가
        #                 "8275000",  # 저가
        #                 "6.19584467"  # 거래량
        #             ],
        #         }
        #     }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades(public)
        #
        #     {
        #         "transaction_date":"2020-04-23 22:21:46",
        #         "type":"ask",
        #         "units_traded":"0.0125",
        #         "price":"8667000",
        #         "total":"108337"
        #     }
        #
        # fetchOrder(private)
        #
        #     {
        #         "transaction_date": "1572497603902030",
        #         "price": "8601000",
        #         "units": "0.005",
        #         "fee_currency": "KRW",
        #         "fee": "107.51",
        #         "total": "43005"
        #     }
        #
        # a workaround for their bug in date format, hours are not 0-padded
        timestamp = None
        transactionDatetime = self.safe_string(trade, 'transaction_date')
        if transactionDatetime is not None:
            parts = transactionDatetime.split(' ')
            numParts = len(parts)
            if numParts > 1:
                transactionDate = parts[0]
                transactionTime = parts[1]
                if len(transactionTime) < 8:
                    transactionTime = '0' + transactionTime
                timestamp = self.parse8601(transactionDate + ' ' + transactionTime)
            else:
                timestamp = self.safe_integer_product(trade, 'transaction_date', 0.001)
        if timestamp is not None:
            timestamp -= 9 * 3600000  # they report UTC + 9 hours, server in Korean timezone
        type = None
        side = self.safe_string(trade, 'type')
        side = 'sell' if (side == 'ask') else 'buy'
        id = self.safe_string(trade, 'cont_no')
        market = self.safe_market(None, market)
        priceString = self.safe_string(trade, 'price')
        amountString = self.fix_comma_number(self.safe_string_2(trade, 'units_traded', 'units'))
        costString = self.safe_string(trade, 'total')
        fee = None
        feeCostString = self.safe_string(trade, 'fee')
        if feeCostString is not None:
            feeCurrencyId = self.safe_string(trade, 'fee_currency')
            feeCurrencyCode = self.common_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
            }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'order': None,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
        }, market)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://apidocs.bithumb.com/reference/%EC%B5%9C%EA%B7%BC-%EC%B2%B4%EA%B2%B0-%EB%82%B4%EC%97%AD
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'baseId': market['baseId'],
            'quoteId': market['quoteId'],
        }
        if limit is not None:
            request['count'] = limit  # default 20, max 100
        response = await self.publicGetTransactionHistoryBaseIdQuoteId(self.extend(request, params))
        #
        #     {
        #         "status":"0000",
        #         "data":[
        #             {
        #                 "transaction_date":"2020-04-23 22:21:46",
        #                 "type":"ask",
        #                 "units_traded":"0.0125",
        #                 "price":"8667000",
        #                 "total":"108337"
        #             },
        #         ]
        #     }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://apidocs.bithumb.com/reference/%EC%A7%80%EC%A0%95%EA%B0%80-%EC%A3%BC%EB%AC%B8%ED%95%98%EA%B8%B0
        :see: https://apidocs.bithumb.com/reference/%EC%8B%9C%EC%9E%A5%EA%B0%80-%EB%A7%A4%EC%88%98%ED%95%98%EA%B8%B0
        :see: https://apidocs.bithumb.com/reference/%EC%8B%9C%EC%9E%A5%EA%B0%80-%EB%A7%A4%EB%8F%84%ED%95%98%EA%B8%B0
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'order_currency': market['id'],
            'payment_currency': market['quote'],
            'units': amount,
        }
        method = 'privatePostTradePlace'
        if type == 'limit':
            request['price'] = price
            request['type'] = 'bid' if (side == 'buy') else 'ask'
        else:
            method = 'privatePostTradeMarket' + self.capitalize(side)
        response = await getattr(self, method)(self.extend(request, params))
        id = self.safe_string(response, 'order_id')
        if id is None:
            raise InvalidOrder(self.id + ' createOrder() did not return an order id')
        return self.safe_order({
            'info': response,
            'symbol': symbol,
            'type': type,
            'side': side,
            'id': id,
        }, market)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://apidocs.bithumb.com/reference/%EA%B1%B0%EB%9E%98-%EC%A3%BC%EB%AC%B8%EB%82%B4%EC%97%AD-%EC%83%81%EC%84%B8-%EC%A1%B0%ED%9A%8C
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'order_id': id,
            'count': 1,
            'order_currency': market['base'],
            'payment_currency': market['quote'],
        }
        response = await self.privatePostInfoOrderDetail(self.extend(request, params))
        #
        #     {
        #         "status": "0000",
        #         "data": {
        #             "order_date": "1603161798539254",
        #             "type": "ask",
        #             "order_status": "Cancel",
        #             "order_currency": "BTC",
        #             "payment_currency": "KRW",
        #             "watch_price": "0",
        #             "order_price": "13344000",
        #             "order_qty": "0.0125",
        #             "cancel_date": "1603161803809993",
        #             "cancel_type": "사용자취소",
        #             "contract": [
        #                 {
        #                     "transaction_date": "1603161799976383",
        #                     "price": "13344000",
        #                     "units": "0.0015",
        #                     "fee_currency": "KRW",
        #                     "fee": "0",
        #                     "total": "20016"
        #                 }
        #             ],
        #         }
        #     }
        #
        data = self.safe_dict(response, 'data')
        return self.parse_order(self.extend(data, {'order_id': id}), market)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'Pending': 'open',
            'Completed': 'closed',
            'Cancel': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        #
        # fetchOrder
        #
        #     {
        #         "transaction_date": "1572497603668315",
        #         "type": "bid",
        #         "order_status": "Completed",  # Completed, Cancel ...
        #         "order_currency": "BTC",
        #         "payment_currency": "KRW",
        #         "watch_price": "0",  # present in Cancel order
        #         "order_price": "8601000",
        #         "order_qty": "0.007",
        #         "cancel_date": "",  # filled in Cancel order
        #         "cancel_type": "",  # filled in Cancel order, i.e. 사용자취소
        #         "contract": [
        #             {
        #                 "transaction_date": "1572497603902030",
        #                 "price": "8601000",
        #                 "units": "0.005",
        #                 "fee_currency": "KRW",
        #                 "fee": "107.51",
        #                 "total": "43005"
        #             },
        #         ]
        #     }
        #
        # fetchOpenOrders
        #
        #     {
        #         "order_currency": "BTC",
        #         "payment_currency": "KRW",
        #         "order_id": "C0101000007408440032",
        #         "order_date": "1571728739360570",
        #         "type": "bid",
        #         "units": "5.0",
        #         "units_remaining": "5.0",
        #         "price": "501000",
        #     }
        #
        timestamp = self.safe_integer_product(order, 'order_date', 0.001)
        sideProperty = self.safe_value_2(order, 'type', 'side')
        side = 'buy' if (sideProperty == 'bid') else 'sell'
        status = self.parse_order_status(self.safe_string(order, 'order_status'))
        price = self.safe_string_2(order, 'order_price', 'price')
        type = 'limit'
        if Precise.string_equals(price, '0'):
            type = 'market'
        amount = self.fix_comma_number(self.safe_string_2(order, 'order_qty', 'units'))
        remaining = self.fix_comma_number(self.safe_string(order, 'units_remaining'))
        if remaining is None:
            if status == 'closed':
                remaining = '0'
            elif status != 'canceled':
                remaining = amount
        symbol = None
        baseId = self.safe_string(order, 'order_currency')
        quoteId = self.safe_string(order, 'payment_currency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        if (base is not None) and (quote is not None):
            symbol = base + '/' + quote
        if symbol is None:
            market = self.safe_market(None, market)
            symbol = market['symbol']
        id = self.safe_string(order, 'order_id')
        rawTrades = self.safe_value(order, 'contract', [])
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': None,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': rawTrades,
        }, market)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://apidocs.bithumb.com/reference/%EA%B1%B0%EB%9E%98-%EC%A3%BC%EB%AC%B8%EB%82%B4%EC%97%AD-%EC%A1%B0%ED%9A%8C
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of open order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        if limit is None:
            limit = 100
        request: dict = {
            'count': limit,
            'order_currency': market['base'],
            'payment_currency': market['quote'],
        }
        if since is not None:
            request['after'] = since
        response = await self.privatePostInfoOrders(self.extend(request, params))
        #
        #     {
        #         "status": "0000",
        #         "data": [
        #             {
        #                 "order_currency": "BTC",
        #                 "payment_currency": "KRW",
        #                 "order_id": "C0101000007408440032",
        #                 "order_date": "1571728739360570",
        #                 "type": "bid",
        #                 "units": "5.0",
        #                 "units_remaining": "5.0",
        #                 "price": "501000",
        #             }
        #         ]
        #     }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://apidocs.bithumb.com/reference/%EC%A3%BC%EB%AC%B8-%EC%B7%A8%EC%86%8C%ED%95%98%EA%B8%B0
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        side_in_params = ('side' in params)
        if not side_in_params:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a `side` parameter(sell or buy)')
        market = self.market(symbol)
        side = 'bid' if (params['side'] == 'buy') else 'ask'
        params = self.omit(params, ['side', 'currency'])
        # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/6771
        request: dict = {
            'order_id': id,
            'type': side,
            'order_currency': market['base'],
            'payment_currency': market['quote'],
        }
        response = await self.privatePostTradeCancel(self.extend(request, params))
        #
        #    {
        #       'status': 'string',
        #    }
        #
        return self.safe_order({
            'info': response,
        })

    async def cancel_unified_order(self, order, params={}):
        request: dict = {
            'side': order['side'],
        }
        return await self.cancel_order(order['id'], order['symbol'], self.extend(request, params))

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://apidocs.bithumb.com/reference/%EC%BD%94%EC%9D%B8-%EC%B6%9C%EA%B8%88%ED%95%98%EA%B8%B0-%EA%B0%9C%EC%9D%B8
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'units': amount,
            'address': address,
            'currency': currency['id'],
        }
        if code == 'XRP' or code == 'XMR' or code == 'EOS' or code == 'STEEM':
            destination = self.safe_string(params, 'destination')
            if (tag is None) and (destination is None):
                raise ArgumentsRequired(self.id + ' ' + code + ' withdraw() requires a tag argument or an extra destination param')
            elif tag is not None:
                request['destination'] = tag
        response = await self.privatePostTradeBtcWithdrawal(self.extend(request, params))
        #
        # {"status" : "0000"}
        #
        return self.parse_transaction(response, currency)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # withdraw
        #
        #     {"status" : "0000"}
        #
        currency = self.safe_currency(None, currency)
        return {
            'id': None,
            'txid': None,
            'timestamp': None,
            'datetime': None,
            'network': None,
            'addressFrom': None,
            'address': None,
            'addressTo': None,
            'amount': None,
            'type': None,
            'currency': currency['code'],
            'status': None,
            'updated': None,
            'tagFrom': None,
            'tag': None,
            'tagTo': None,
            'comment': None,
            'internal': None,
            'fee': None,
            'info': transaction,
        }

    def fix_comma_number(self, numberStr):
        # some endpoints need self https://github.com/ccxt_zagrava/ccxt_zagrava/issues/11031
        if numberStr is None:
            return None
        finalNumberStr = numberStr
        while(finalNumberStr.find(',') > -1):
            finalNumberStr = finalNumberStr.replace(',', '')
        return finalNumberStr

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        endpoint = '/' + self.implode_params(path, params)
        url = self.implode_hostname(self.urls['api'][api]) + endpoint
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        else:
            self.check_required_credentials()
            body = self.urlencode(self.extend({
                'endpoint': endpoint,
            }, query))
            nonce = str(self.nonce())
            auth = endpoint + "\0" + body + "\0" + nonce  # eslint-disable-line quotes
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha512)
            signature64 = self.string_to_base64(signature)
            headers = {
                'Accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Api-Key': self.apiKey,
                'Api-Sign': signature64,
                'Api-Nonce': nonce,
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None  # fallback to default error handler
        if 'status' in response:
            #
            #     {"status":"5100","message":"After May 23th, recent_transactions is no longer, hence users will not be able to connect to recent_transactions"}
            #
            status = self.safe_string(response, 'status')
            message = self.safe_string(response, 'message')
            if status is not None:
                if status == '0000':
                    return None  # no error
                elif message == '거래 진행중인 내역이 존재하지 않습니다.':
                    # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/9017
                    return None  # no error
                feedback = self.id + ' ' + message
                self.throw_exactly_matched_exception(self.exceptions, status, feedback)
                self.throw_exactly_matched_exception(self.exceptions, message, feedback)
                raise ExchangeError(feedback)
        return None
