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

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

from ccxt.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidAddress
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.decimal_to_precision import ROUND
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import TICK_SIZE


class gopax(Exchange):

    def describe(self):
        return self.deep_extend(super(gopax, self).describe(), {
            'id': 'gopax',
            'name': 'GOPAX',
            'countries': ['KR'],  # South Korea
            'version': 'v1',
            'rateLimit': 50,
            'hostname': 'gopax.co.kr',  # or 'gopax.com'
            'certified': True,
            'pro': True,
            'has': {
                'cancelOrder': True,
                'createMarketOrder': True,
                'createOrder': True,
                'fetchBalance': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': 'emulated',
                'fetchDepositAddresses': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTransactions': True,
            },
            'timeframes': {
                '1m': '1',
                '5m': '5',
                '30m': '30',
                '1d': '1440',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/102897212-ae8a5e00-4478-11eb-9bab-91507c643900.jpg',
                'api': {
                    'public': 'https://api.{hostname}',  # or 'https://api.gopax.co.kr'
                    'private': 'https://api.{hostname}',
                },
                'www': 'https://www.gopax.co.kr',
                'doc': 'https://gopax.github.io/API/index.en.html',
                'fees': 'https://www.gopax.com/feeinfo',
            },
            'api': {
                'public': {
                    'get': [
                        'notices',
                        'assets',
                        'price-tick-size',
                        'trading-pairs',
                        'trading-pairs/{tradingPair}/ticker',
                        'trading-pairs/{tradingPair}/book',
                        'trading-pairs/{tradingPair}/trades',
                        'trading-pairs/{tradingPair}/stats',
                        'trading-pairs/{tradingPair}/price-tick-size',
                        'trading-pairs/stats',
                        'trading-pairs/{tradingPair}/candles',
                        'time',
                    ],
                },
                'private': {
                    'get': [
                        'balances',
                        'balances/{assetName}',
                        'orders',
                        'orders/{orderId}',
                        'orders/clientOrderId/{clientOrderId}',
                        'trades',
                        'deposit-withdrawal-status',
                        'crypto-deposit-addresses',
                        'crypto-withdrawal-addresses',
                    ],
                    'post': [
                        'orders',
                    ],
                    'delete': [
                        'orders/{orderId}',
                        'orders/clientOrderId/{clientOrderId}',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'percentage': True,
                    'tierBased': False,
                    'maker': 0.04 / 100,
                    'taker': 0.04 / 100,
                },
            },
            'exceptions': {
                'broad': {
                    'ERROR_INVALID_ORDER_TYPE': InvalidOrder,
                    'ERROR_INVALID_AMOUNT': InvalidOrder,
                    'ERROR_INVALID_TRADING_PAIR': BadSymbol,  # Unlikely to be triggered, due to ccxt.gopax.js implementation
                    'No such order ID': OrderNotFound,  # {"errorMessage":"No such order ID","errorCode":202,"errorData":"Order server error: 202"}
                    # 'Not enough amount': InsufficientFunds,  # {"errorMessage":"Not enough amount, try increasing your order amount","errorCode":10212,"errorData":{}}
                    'Forbidden order type': InvalidOrder,
                    'the client order ID will be reusable which order has already been completed or canceled': InvalidOrder,
                    'ERROR_NO_SUCH_TRADING_PAIR': BadSymbol,  # Unlikely to be triggered, due to ccxt.gopax.js implementation
                    'ERROR_INVALID_ORDER_SIDE': InvalidOrder,
                    'ERROR_NOT_HEDGE_TOKEN_USER': InvalidOrder,
                    'ORDER_EVENT_ERROR_NOT_ALLOWED_BID_ORDER': InvalidOrder,  # Triggered only when the exchange is locked
                    'ORDER_EVENT_ERROR_INSUFFICIENT_BALANCE': InsufficientFunds,
                    'Invalid option combination': InvalidOrder,
                    'No such client order ID': OrderNotFound,
                },
                'exact': {
                    '100': BadSymbol,  # Invalid asset name
                    '101': BadSymbol,  # Invalid trading pair
                    '103': InvalidOrder,  # Invalid order type
                    '104': BadSymbol,  # Invalid trading pair
                    '105': BadSymbol,  # Trading pair temporarily disabled
                    '106': BadSymbol,  # Invalid asset name
                    '107': InvalidOrder,  # Invalid order amount
                    '108': InvalidOrder,  # Invalid order price
                    '111': InvalidOrder,  # Invalid event type
                    '201': InsufficientFunds,  # Not enough balance
                    '202': InvalidOrder,  # Invalid order ID
                    '203': InvalidOrder,  # Order amount X order price too large
                    '204': InvalidOrder,  # Bid order temporarily unavailable
                    '205': InvalidOrder,  # Invalid side
                    '206': InvalidOrder,  # Invalid order option combination
                    '10004': AuthenticationError,  # Not authorized
                    # '10004': ExchangeError,  # API key not exist
                    # '10004': ExchangeError,  # User KYC not approved
                    # '10004': ExchangeError,  # User account is frozen
                    # '10004': ExchangeError,  # User is under deactivation process
                    # '10004': ExchangeError,  # 2FA is not enabled
                    # '10004': ExchangeError,  # Invalid signature
                    '10041': BadRequest,  # Invalid exchange
                    '10056': BadRequest,  # No registered asset
                    '10057': BadSymbol,  # No registered trading pair
                    '10059': BadSymbol,  # Invalid trading pair
                    '10062': BadRequest,  # Invalid chart interval
                    '10069': OrderNotFound,  # {"errorMessage":"No such order ID: 73152094","errorCode":10069,"errorData":"73152094"}
                    '10155': AuthenticationError,  # {"errorMessage":"Invalid API key","errorCode":10155}
                    '10166': BadRequest,  # Invalid chart range
                    '10212': InvalidOrder,  # {"errorMessage":"Not enough amount, try increasing your order amount","errorCode":10212,"errorData":{}}
                    '10221': OrderNotFound,  # No such client order ID
                    '10222': InvalidOrder,  # Client order ID being used
                    '10223': InvalidOrder,  # Soon the client order ID will be reusable which order has already been completed or canceled
                    '10227': InvalidOrder,  # Invalid client order ID format
                    '10319': BadRequest,  # Pagination is required as you have too many orders
                    '10358': InvalidOrder,  # Invalid order type
                    '10359': InvalidOrder,  # Invalid order side
                    '10360': InvalidOrder,  # Invalid order status
                    '10361': InvalidOrder,  # Invalid order time in force
                    '10362': InvalidOrder,  # Invalid order protection
                    '10363': InvalidOrder,  # Invalid forced completion reason
                },
            },
            'options': {
                'createMarketBuyOrderRequiresPrice': True,
            },
        })

    def fetch_time(self, params={}):
        response = self.publicGetTime(params)
        #
        #     {"serverTime":1608327726656}
        #
        return self.safe_integer(response, 'serverTime')

    def fetch_markets(self, params={}):
        response = self.publicGetTradingPairs(params)
        #
        #     [
        #         {
        #             "id":1,
        #             "name":"ETH-KRW",
        #             "baseAsset":"ETH",
        #             "quoteAsset":"KRW",
        #             "baseAssetScale":8,
        #             "quoteAssetScale":0,
        #             "priceMin":1,
        #             "restApiOrderAmountMin":{
        #                 "limitAsk":{"amount":10000,"unit":"KRW"},
        #                 "limitBid":{"amount":10000,"unit":"KRW"},
        #                 "marketAsk":{"amount":0.001,"unit":"ETH"},
        #                 "marketBid":{"amount":10000,"unit":"KRW"},
        #             },
        #             "makerFeePercent":0.2,
        #             "takerFeePercent":0.2,
        #         },
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'name')
            numericId = self.safe_integer(market, 'id')
            baseId = self.safe_string(market, 'baseAsset')
            quoteId = self.safe_string(market, 'quoteAsset')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'price': self.safe_integer(market, 'quoteAssetScale'),
                'amount': self.safe_integer(market, 'baseAssetScale'),
            }
            minimums = self.safe_value(market, 'restApiOrderAmountMin', {})
            marketAsk = self.safe_value(minimums, 'marketAsk', {})
            marketBid = self.safe_value(minimums, 'marketBid', {})
            takerFeePercent = self.safe_float(market, 'takerFeePercent')
            makerFeePercent = self.safe_float(market, 'makerFeePercent')
            taker = float(self.decimal_to_precision(takerFeePercent / 100, ROUND, 0.00000001, TICK_SIZE))
            maker = float(self.decimal_to_precision(makerFeePercent / 100, ROUND, 0.00000001, TICK_SIZE))
            result.append({
                'id': id,
                'info': market,
                'numericId': numericId,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': self.safe_string(market, 'baseAsset'),
                'quoteId': self.safe_string(market, 'quoteAsset'),
                'active': True,
                'taker': taker,
                'maker': maker,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': self.safe_float(marketAsk, 'amount'),
                        'max': None,
                    },
                    'price': {
                        'min': self.safe_float(market, 'priceMin'),
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_float(marketBid, 'amount'),
                        'max': None,
                    },
                },
            })
        return result

    def fetch_currencies(self, params={}):
        response = self.publicGetAssets(params)
        #
        #     [
        #         {
        #             "id":"KRW",
        #             "name":"대한민국 원",
        #             "scale":0,
        #             "withdrawalFee":1000,
        #             "withdrawalAmountMin":5000
        #         },
        #         {
        #             "id":"ETH",
        #             "name":"이더리움",
        #             "scale":8,
        #             "withdrawalFee":0.03,
        #             "withdrawalAmountMin":0.015
        #         },
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'id')
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'name')
            fee = self.safe_float(currency, 'withdrawalFee')
            precision = self.safe_float(currency, 'scale')
            result[code] = {
                'id': id,
                'info': currency,
                'code': code,
                'name': name,
                'active': True,
                'fee': fee,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(currency, 'withdrawalAmountMin'),
                        'max': None,
                    },
                },
            }
        return result

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'tradingPair': market['id'],
            # 'level': 3,  # 1 best bidask, 2 top 50 bidasks, 3 all bidasks
        }
        response = self.publicGetTradingPairsTradingPairBook(self.extend(request, params))
        #
        #     {
        #         "sequence":17691957,
        #         "bid":[
        #             ["17690499",25019000,0.00008904,"1608326468921"],
        #             ["17691894",25010000,0.4295,"1608326499940"],
        #             ["17691895",25009000,0.2359,"1608326499953"],
        #         ],
        #         "ask":[
        #             ["17689176",25024000,0.000098,"1608326442006"],
        #             ["17691351",25031000,0.206,"1608326490418"],
        #             ["17691571",25035000,0.3996,"1608326493742"],
        #         ]
        #     }
        #
        nonce = self.safe_integer(response, 'sequence')
        result = self.parse_order_book(response, None, 'bid', 'ask', 1, 2)
        result['nonce'] = nonce
        return result

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker
        #
        #     {
        #         "price":25087000,
        #         "ask":25107000,
        #         "askVolume":0.05837704,
        #         "bid":25087000,
        #         "bidVolume":0.00398628,
        #         "volume":350.09171591,
        #         "quoteVolume":8721016926.06529,
        #         "time":"2020-12-18T21:42:13.774Z",
        #     }
        #
        # fetchTickers
        #
        #     {
        #         "name":"ETH-KRW",
        #         "open":690500,
        #         "high":719500,
        #         "low":681500,
        #         "close":709500,
        #         "volume":2784.6081544,
        #         "time":"2020-12-18T21:54:50.795Z"
        #     }
        #
        marketId = self.safe_string(ticker, 'name')
        symbol = self.safe_symbol(marketId, market, '-')
        timestamp = self.parse8601(self.safe_string(ticker, 'time'))
        open = self.safe_float(ticker, 'open')
        last = self.safe_float_2(ticker, 'price', 'close')
        change = None
        percentage = None
        average = None
        if (last is not None) and (open is not None):
            average = self.sum(last, open) / 2
            change = last - open
            if open > 0:
                percentage = change / open * 100
        baseVolume = self.safe_float(ticker, 'volume')
        quoteVolume = self.safe_float(ticker, 'quoteVolume')
        vwap = self.vwap(baseVolume, quoteVolume)
        return {
            'symbol': symbol,
            'info': ticker,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'bid'),
            'bidVolume': self.safe_float(ticker, 'bidVolume'),
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': self.safe_float(ticker, 'askVolume'),
            'vwap': vwap,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'tradingPair': market['id'],
        }
        response = self.publicGetTradingPairsTradingPairTicker(self.extend(request, params))
        #
        #     {
        #         "price":25087000,
        #         "ask":25107000,
        #         "askVolume":0.05837704,
        #         "bid":25087000,
        #         "bidVolume":0.00398628,
        #         "volume":350.09171591,
        #         "quoteVolume":8721016926.06529,
        #         "time":"2020-12-18T21:42:13.774Z",
        #     }
        #
        return self.parse_ticker(response, market)

    def parse_tickers(self, rawTickers, symbols=None):
        tickers = []
        for i in range(0, len(rawTickers)):
            tickers.append(self.parse_ticker(rawTickers[i]))
        return self.filter_by_array(tickers, 'symbol', symbols)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = self.publicGetTradingPairsStats(params)
        #
        #     [
        #         {
        #             "name":"ETH-KRW",
        #             "open":690500,
        #             "high":719500,
        #             "low":681500,
        #             "close":709500,
        #             "volume":2784.6081544,
        #             "time":"2020-12-18T21:54:50.795Z"
        #         }
        #     ]
        #
        return self.parse_tickers(response, symbols)

    def parse_public_trade(self, trade, market=None):
        timestamp = self.parse8601(self.safe_string(trade, 'time'))
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        symbol = None
        if 'symbol' in market:
            symbol = self.safe_string(market, 'symbol')
        return {
            'info': trade,
            'id': self.safe_string(trade, 'id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': None,  # Not mandatory to specify
            'type': None,  # Not mandatory to specify
            'side': self.safe_string(trade, 'side'),
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': price * amount,
            'fee': None,
        }

    def parse_private_trade(self, trade, market=None):
        timestamp = self.parse8601(self.safe_string(trade, 'timestamp'))
        symbol = self.safe_string(trade, 'tradingPairName').replace('-', '/')
        side = self.safe_string(trade, 'side')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'baseAmount')
        feeCurrency = symbol[0:3]
        if side == 'sell':
            feeCurrency = symbol[4:]
        fee = {
            'cost': self.safe_float(trade, 'fee'),
            'currency': feeCurrency,
            'rate': None,
        }
        return {
            'info': trade,
            'id': self.safe_string(trade, 'id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': self.safe_integer(trade, 'orderId'),
            'type': None,
            'side': side,
            'takerOrMaker': self.safe_string(trade, 'position'),
            'price': price,
            'amount': amount,
            'cost': price * amount,
            'fee': fee,
        }

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #     {
        #         "time":"2020-12-19T12:17:43.000Z",
        #         "date":1608380263,
        #         "id":23903608,
        #         "price":25155000,
        #         "amount":0.0505,
        #         "side":"sell",
        #     }
        #
        # private fetchMyTrades
        #
        #     {
        #         "id": 73953,                             # trading event ID
        #         "orderId": 453324,                       # order ID
        #         "baseAmount": 3,                         # traded base asset amount
        #         "quoteAmount": 3000000,                  # traded quote asset amount
        #         "fee": 0.0012,                           # fee
        #         "price": 1000000,                        # price
        #         "timestamp": "2020-09-25T04:06:30.000Z",  # trading time
        #         "side": "buy",                           # buy, sell
        #         "tradingPairName": "ZEC-KRW",            # order book
        #         "position": "maker"                      # maker, taker
        #     }
        #
        #     {
        #         "tradeId": 74072,            # trade ID
        #         "orderId": 453529,           # order ID
        #         "side": 2,                   # 1(bid), 2(ask)
        #         "type": 1,                   # 1(limit), 2(market)
        #         "baseAmount": 0.01,          # filled base asset amount(in ZEC for self case)
        #         "quoteAmount": 1,            # filled quote asset amount(in KRW for self case)
        #         "fee": 0.0004,               # fee
        #         "price": 100,                # price
        #         "isSelfTrade": False,        # whether both of matching orders are yours
        #         "occurredAt": 1603932107,    # trade occurrence time
        #         "tradingPairName": "ZEC-KRW"  # order book
        #     }
        #
        id = self.safe_string_2(trade, 'id', 'tradeId')
        orderId = self.safe_integer(trade, 'orderId')
        timestamp = self.parse8601(self.safe_string_2(trade, 'time', 'timestamp'))
        timestamp = self.safe_timestamp(trade, 'occuredAt', timestamp)
        marketId = self.safe_string(trade, 'tradingPairName')
        market = self.safe_market(marketId, market, '-')
        symbol = market['symbol']
        side = self.safe_string(trade, 'side')
        if side == '1':
            side = 'buy'
        elif side == '2':
            side = 'sell'
        type = self.safe_string(trade, 'type')
        if type == '1':
            type = 'limit'
        elif type == '2':
            type = 'market'
        price = self.safe_float(trade, 'price')
        amount = self.safe_float_2(trade, 'amount', 'baseAmount')
        cost = self.safe_float(trade, 'quoteAmount')
        if cost is None:
            if (price is not None) and (amount is not None):
                cost = price * amount
        feeCost = self.safe_float(trade, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': market['base'],
            }
        takerOrMaker = self.safe_string(trade, 'position')
        return {
            'info': trade,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'tradingPair': market['id'],
            # 'limit': limit,
            # 'pastmax': id,  # read data older than self ID
            # 'latestmin': id,  # read data newer than self ID
            # 'after': int(since / 1000),
            # 'before': self.seconds(),
        }
        if since is not None:
            request['after'] = int(since / 1000)
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetTradingPairsTradingPairTrades(self.extend(request, params))
        #
        #     [
        #         {"time":"2020-12-19T12:17:43.000Z","date":1608380263,"id":23903608,"price":25155000,"amount":0.0505,"side":"sell"},
        #         {"time":"2020-12-19T12:17:13.000Z","date":1608380233,"id":23903604,"price":25140000,"amount":0.019,"side":"sell"},
        #         {"time":"2020-12-19T12:16:49.000Z","date":1608380209,"id":23903599,"price":25140000,"amount":0.0072,"side":"sell"},
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1606780800000,  # timestamp
        #         21293000,      # low
        #         21300000,      # high
        #         21294000,      # open
        #         21300000,      # close
        #         1.019126,      # volume
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_float(ohlcv, 3),
            self.safe_float(ohlcv, 2),
            self.safe_float(ohlcv, 1),
            self.safe_float(ohlcv, 4),
            self.safe_float(ohlcv, 5),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        limit = 1024 if (limit is None) else limit  # default 1024
        request = {
            'tradingPair': market['id'],
            # 'start': since,
            # 'end': self.milliseconds(),
            'interval': self.timeframes[timeframe],
        }
        duration = self.parse_timeframe(timeframe)
        if since is None:
            end = self.milliseconds()
            request['end'] = end
            request['start'] = end - limit * duration * 1000
        else:
            request['start'] = since
            request['end'] = self.sum(since, limit * duration * 1000)
        response = self.publicGetTradingPairsTradingPairCandles(self.extend(request, params))
        #
        #     [
        #         [1606780800000,21293000,21300000,21294000,21300000,1.019126],
        #         [1606780860000,21237000,21293000,21293000,21263000,0.96800057],
        #         [1606780920000,21240000,21240000,21240000,21240000,0.11068715],
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_balance_response(self, response):
        result = {'info': response}
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string_2(balance, 'asset', 'isoAlpha3')
            code = self.safe_currency_code(currencyId)
            hold = self.safe_float(balance, 'hold')
            pendingWithdrawal = self.safe_float(balance, 'pendingWithdrawal')
            account = self.account()
            account['free'] = self.safe_float(balance, 'avail')
            account['used'] = self.sum(hold, pendingWithdrawal)
            result[code] = account
        return self.parse_balance(result)

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privateGetBalances(params)
        #
        #     [
        #         {
        #             "asset": "KRW",                   # asset name
        #             "avail": 1759466.76,              # available amount to place order
        #             "hold": 16500,                    # outstanding amount on order books
        #             "pendingWithdrawal": 0,           # amount being withdrawn
        #             "lastUpdatedAt": "1600684352032",  # balance last update time
        #         },
        #     ]
        #
        return self.parse_balance_response(response)

    def parse_order_status(self, status):
        statuses = {
            'placed': 'open',
            'cancelled': 'canceled',
            'completed': 'closed',
            'updated': 'open',
            'reserved': 'open',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # cancelOrder
        #
        #     {}  # empty object
        #
        # fetchOrder, fetchOrders, fetchOpenOrders, createOrder
        #
        #     {
        #         "id": "453324",                          # order ID
        #         "clientOrderId": "zeckrw23456",          # client order ID(showed only when it exists)
        #         "status": "updated",                     # placed, cancelled, completed, updated, reserved
        #         "forcedCompletionReason": None,     # the reason in case it was canceled in the middle(protection or timeInForce)
        #         "tradingPairName": "ZEC-KRW",            # order book
        #         "side": "buy",                           # buy, sell
        #         "type": "limit",                         # limit, market
        #         "price": 1000000,                        # price
        #         "stopPrice": None,                  # stop price(showed only for stop orders)
        #         "amount": 4,                             # initial amount
        #         "remaining": 1,                          # outstanding amount
        #         "protection": "yes",                     # whether protection is activated(yes or no)
        #         "timeInForce": "gtc",                    # limit order's time in force(gtc/po/ioc/fok)
        #         "createdAt": "2020-09-25T04:06:20.000Z",  # order placement time
        #         "updatedAt": "2020-09-25T04:06:29.000Z",  # order last update time
        #         "balanceChange": {
        #             "baseGross": 3,                      # base asset balance's gross change(in ZEC for self case)
        #             "baseFee": {
        #                 "taking": 0,                     # base asset fee imposed as taker
        #                 "making": -0.0012                # base asset fee imposed as maker
        #             },
        #             "baseNet": 2.9988,                   # base asset balance's net change(in ZEC for self case)
        #             "quoteGross": -3000000,              # quote asset balance's gross change(in KRW for
        #             "quoteFee": {
        #                 "taking": 0,                     # quote asset fee imposed as taker
        #                 "making": 0                      # quote asset fee imposed as maker
        #             },
        #             "quoteNet": -3000000                 # quote asset balance's net change(in KRW for self case)
        #         }
        #     }
        #
        id = self.safe_string(order, 'id')
        clientOrderId = self.safe_string(order, 'clientOrderId')
        timestamp = self.parse8601(self.safe_string(order, 'createdAt'))
        type = self.safe_string(order, 'type')
        side = self.safe_string(order, 'side')
        timeInForce = self.safe_string_upper(order, 'timeInForce')
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'amount')
        stopPrice = self.safe_float(order, 'stopPrice')
        remaining = self.safe_float(order, 'remaining')
        marketId = self.safe_string(order, 'tradingPairName')
        market = self.safe_market(marketId, market, '-')
        status = self.parse_order_status(self.safe_string(order, 'status'))
        balanceChange = self.safe_value(order, 'balanceChange', {})
        filled = self.safe_float(balanceChange, 'baseNet')
        cost = self.safe_float(balanceChange, 'quoteNet')
        if cost is not None:
            cost = abs(cost)
        updated = None
        if (filled is None) and (amount is not None) and (remaining is not None):
            filled = max(0, amount - remaining)
        if (filled is not None) and (filled > 0):
            updated = self.parse8601(self.safe_string(order, 'updatedAt'))
        if (cost is None) and (price is not None) and (filled is not None):
            cost = filled * price
        fee = None
        if side == 'buy':
            baseFee = self.safe_value(balanceChange, 'baseFee', {})
            taking = self.safe_float(baseFee, 'taking')
            making = self.safe_float(baseFee, 'making')
            fee = {
                'currency': market['base'],
                'cost': self.sum(taking, making),
            }
        else:
            quoteFee = self.safe_value(balanceChange, 'quoteFee', {})
            taking = self.safe_float(quoteFee, 'taking')
            making = self.safe_float(quoteFee, 'making')
            fee = {
                'currency': market['quote'],
                'cost': self.sum(taking, making),
            }
        postOnly = None
        if timeInForce is not None:
            postOnly = (timeInForce == 'PO')
        return {
            'id': id,
            'clientOrderId': clientOrderId,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': updated,
            'status': status,
            'symbol': market['symbol'],
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'average': None,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'cost': cost,
            'trades': None,
            'fee': fee,
            'info': order,
        }

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        method = None
        clientOrderId = self.safe_string(params, 'clientOrderId')
        params = self.omit(params, 'clientOrderId')
        request = {}
        if clientOrderId is None:
            method = 'privateGetOrdersOrderId'
            request['orderId'] = id
        else:
            method = 'privateGetOrdersClientOrderIdClientOrderId'
            request['clientOrderId'] = clientOrderId
        response = getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "id": "453324",                          # order ID
        #         "clientOrderId": "zeckrw23456",          # client order ID(showed only when it exists)
        #         "status": "updated",                     # placed, cancelled, completed, updated, reserved
        #         "forcedCompletionReason": None,     # the reason in case it was canceled in the middle(protection or timeInForce)
        #         "tradingPairName": "ZEC-KRW",            # order book
        #         "side": "buy",                           # buy, sell
        #         "type": "limit",                         # limit, market
        #         "price": 1000000,                        # price
        #         "stopPrice": None,                  # stop price(showed only for stop orders)
        #         "amount": 4,                             # initial amount
        #         "remaining": 1,                          # outstanding amount
        #         "protection": "yes",                     # whether protection is activated(yes or no)
        #         "timeInForce": "gtc",                    # limit order's time in force(gtc/po/ioc/fok)
        #         "createdAt": "2020-09-25T04:06:20.000Z",  # order placement time
        #         "updatedAt": "2020-09-25T04:06:29.000Z",  # order last update time
        #         "balanceChange": {
        #             "baseGross": 3,                      # base asset balance's gross change(in ZEC for self case)
        #             "baseFee": {
        #                 "taking": 0,                     # base asset fee imposed as taker
        #                 "making": -0.0012                # base asset fee imposed as maker
        #             },
        #             "baseNet": 2.9988,                   # base asset balance's net change(in ZEC for self case)
        #             "quoteGross": -3000000,              # quote asset balance's gross change(in KRW for
        #             "quoteFee": {
        #                 "taking": 0,                     # quote asset fee imposed as taker
        #                 "making": 0                      # quote asset fee imposed as maker
        #             },
        #             "quoteNet": -3000000                 # quote asset balance's net change(in KRW for self case)
        #         }
        #     }
        #
        return self.parse_order(response)

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'includePast': 'true',  # if True, completed and canceled orders are included as the result, they are accessible for one hour only from its completion or cancellation time
            # 'pagination': 'false',  # if the result is more than 3,000 orders, set self value as True to access 1000 orders at max per each page
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = self.privateGetOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": "453324",                          # order ID
        #             "clientOrderId": "zeckrw23456",          # client order ID(showed only when it exists)
        #             "status": "updated",                     # placed, cancelled, completed, updated, reserved
        #             "forcedCompletionReason": None,     # the reason in case it was canceled in the middle(protection or timeInForce)
        #             "tradingPairName": "ZEC-KRW",            # order book
        #             "side": "buy",                           # buy, sell
        #             "type": "limit",                         # limit, market
        #             "price": 1000000,                        # price
        #             "stopPrice": None,                  # stop price(showed only for stop orders)
        #             "amount": 4,                             # initial amount
        #             "remaining": 1,                          # outstanding amount
        #             "protection": "yes",                     # whether protection is activated(yes or no)
        #             "timeInForce": "gtc",                    # limit order's time in force(gtc/po/ioc/fok)
        #             "createdAt": "2020-09-25T04:06:20.000Z",  # order placement time
        #             "updatedAt": "2020-09-25T04:06:29.000Z",  # order last update time
        #             "balanceChange": {
        #                 "baseGross": 3,                      # base asset balance's gross change(in ZEC for self case)
        #                 "baseFee": {
        #                     "taking": 0,                     # base asset fee imposed as taker
        #                     "making": -0.0012                # base asset fee imposed as maker
        #                 },
        #                 "baseNet": 2.9988,                   # base asset balance's net change(in ZEC for self case)
        #                 "quoteGross": -3000000,              # quote asset balance's gross change(in KRW for
        #                 "quoteFee": {
        #                     "taking": 0,                     # quote asset fee imposed as taker
        #                     "making": 0                      # quote asset fee imposed as maker
        #                 },
        #                 "quoteNet": -3000000                 # quote asset balance's net change(in KRW for self case)
        #             }
        #         },
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'includePast': 'false',
        }
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            # 'clientOrderId': 'test4321',  # max 20 characters of [a-zA-Z0-9_-]
            'tradingPairName': market['id'],
            'side': side,  # buy, sell
            'type': type,  # limit, market
            # 'price': self.price_to_precision(symbol, price),
            # 'stopPrice': self.price_to_precision(symbol, stopPrice),  # optional, becomes a stop order if set
            # 'amount': self.amount_to_precision(symbol, amount),
            # 'protection': 'no',  # whether protection is activated
            # 'timeInForce': 'gtc',  # gtc, po, ioc, fok
        }
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
            request['amount'] = self.amount_to_precision(symbol, amount)
        elif type == 'market':
            # for market buy it requires the amount of quote currency to spend
            if side == 'buy':
                total = amount
                createMarketBuyOrderRequiresPrice = self.safe_value(self.options, 'createMarketBuyOrderRequiresPrice', True)
                if createMarketBuyOrderRequiresPrice:
                    if price is None:
                        raise InvalidOrder(self.id + " createOrder() requires the price argument with market buy orders to calculate total order cost(amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = False and supply the total cost value in the 'amount' argument")
                    total = price * amount
                precision = market['precision']['price']
                request['amount'] = self.decimal_to_precision(total, TRUNCATE, precision, self.precisionMode)
            else:
                request['amount'] = self.amount_to_precision(symbol, amount)
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['clientOrderId'] = clientOrderId
            params = self.omit(params, 'clientOrderId')
        stopPrice = self.safe_float(params, 'stopPrice')
        if stopPrice is not None:
            request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
            params = self.omit(params, 'stopPrice')
        timeInForce = self.safe_string_lower(params, 'timeInForce')
        if timeInForce is not None:
            request['timeInForce'] = timeInForce
            params = self.omit(params, 'timeInForce')
        response = self.privatePostOrders(self.extend(request, params))
        #
        #     {
        #         "id": "453327",                          # order ID
        #         "clientOrderId": "test4321",             # client order ID(showed only when it exists)
        #         "status": "reserved",                    # placed, cancelled, completed, updated, reserved
        #         "forcedCompletionReason": None,     # the reason in case it was canceled in the middle(protection or timeInForce)
        #         "tradingPairName": "BCH-KRW",            # order book
        #         "side": "sell",                          # buy, sell
        #         "type": "limit",                         # limit, market
        #         "price": 11000000,                       # price
        #         "stopPrice": 12000000,                   # stop price(showed only for stop orders)
        #         "amount": 0.5,                           # initial amount
        #         "remaining": 0.5,                        # outstanding amount
        #         "protection": "no",                      # whether protection is activated(yes or no)
        #         "timeInForce": "gtc",                    # limit order's time in force(gtc/po/ioc/fok)
        #         "createdAt": "2020-09-25T04:51:31.000Z",  # order placement time
        #         "balanceChange": {
        #             "baseGross": 0,                      # base asset balance's gross change(in BCH for self case)
        #             "baseFee": {
        #                 "taking": 0,                     # base asset fee imposed as taker
        #                 "making": 0                      # base asset fee imposed as maker
        #             },
        #             "baseNet": 0,                        # base asset balance's net change(in BCH for self case)
        #             "quoteGross": 0,                     # quote asset balance's gross change(in KRW for
        #             "quoteFee": {
        #                 "taking": 0,                     # quote asset fee imposed as taker
        #                 "making": 0                      # quote asset fee imposed as maker
        #             },
        #             "quoteNet": 0                        # quote asset balance's net change(in KRW for self case)
        #         }
        #     }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {}
        clientOrderId = self.safe_string(params, 'clientOrderId')
        method = None
        if clientOrderId is None:
            method = 'privateDeleteOrdersOrderId'
            request['orderId'] = id
        else:
            method = 'privateDeleteOrdersClientOrderIdClientOrderId'
            request['clientOrderId'] = clientOrderId
            params = self.omit(params, 'clientOrderId')
        response = getattr(self, method)(self.extend(request, params))
        #
        #     {}
        #
        order = self.parse_order(response)
        return self.extend(order, {'id': id})

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'limit': limit,  # max 100
            # 'pastmax': id,  # read data older than self id
            # 'latestmin': id,  # read data newer than self id
            # 'after': int(since / 1000),  # Read data after self timestamp in seconds
            # 'before': self.seconds(),  # Read data before self timestamp in seconds
            'deepSearch': 'true',  # read data older than one month ago are inclusively looked up only when it is "true"
        }
        if since is not None:
            request['after'] = int(since / 1000)
        if limit is not None:
            request['limit'] = limit
        response = self.privateGetTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": 73953,                             # trading event ID
        #             "orderId": 453324,                       # order ID
        #             "baseAmount": 3,                         # traded base asset amount
        #             "quoteAmount": 3000000,                  # traded quote asset amount
        #             "fee": 0.0012,                           # fee
        #             "price": 1000000,                        # price
        #             "timestamp": "2020-09-25T04:06:30.000Z",  # trading time
        #             "side": "buy",                           # buy, sell
        #             "tradingPairName": "ZEC-KRW",            # order book
        #             "position": "maker"                      # maker, taker
        #         },
        #     ]
        #
        market = None
        if symbol is not None:
            market = self.market(symbol)
        return self.parse_trades(response, market, since, limit)

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     {
        #         "asset": "BTC",                                  # asset name
        #         "address": "1CwC2cMFu1jRQUBtw925cENbT1kctJBMdm",  # deposit address
        #         "memoId": null,                                  # memo ID(showed only for assets using memo ID)
        #         "createdAt": 1594802312                          # deposit address creation time
        #     }
        #
        address = self.safe_string(depositAddress, 'address')
        tag = self.safe_string(depositAddress, 'memoId')
        currencyId = self.safe_string(depositAddress, 'asset')
        code = self.safe_currency_code(currencyId)
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': depositAddress,
        }

    def parse_deposit_addresses(self, addresses, codes=None):
        result = []
        for i in range(0, len(addresses)):
            address = self.parse_deposit_address(addresses[i])
            result.append(address)
        if codes:
            result = self.filter_by_array(result, 'currency', codes)
        return self.index_by(result, 'currency')

    def fetch_deposit_addresses(self, codes=None, params={}):
        self.load_markets()
        response = self.privateGetCryptoDepositAddresses(params)
        #
        #     [
        #         {
        #             "asset": "BTC",                                  # asset name
        #             "address": "1CwC2cMFu1jRQUBtw925cENbT1kctJBMdm",  # deposit address
        #             "memoId": null,                                  # memo ID(showed only for assets using memo ID)
        #             "createdAt": 1594802312                          # deposit address creation time
        #         },
        #     ]
        #
        return self.parse_deposit_addresses(response, codes)

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        response = self.fetch_deposit_addresses(None, params)
        address = self.safe_value(response, code)
        if address is None:
            raise InvalidAddress(self.id + ' fetchDepositAddress() ' + code + ' address not found')
        return address

    def parse_transaction_status(self, status):
        statuses = {
            'reviewing': 'pending',
            'rejected': 'rejected',
            'processing': 'pending',
            'failed': 'failed',
            'completed': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        #     {
        #         "id": 640,                     # deposit/withdrawal event ID
        #         "asset": "BTC",                # asset name
        #         "type": "crypto_withdrawal",   # fiat_withdrawal, fiat_deposit, crypto_withdrawal, crypto_deposit
        #         "netAmount": 0.0001,           # amount
        #         "feeAmount": 0.0005,           # fee(null if there is no imposed fee)
        #         "status": "completed",         # reviewing, rejected, processing, failed, completed
        #         "reviewStartedAt": 1595556218,  # request time
        #         "completedAt": 1595556902,     # completion time(showed only in case of completed)
        #         "txId": "eaca5ad3...",         # tx ID
        #         "sourceAddress": null,         # sender address(showed only in case of crypto_deposit)
        #         "destinationAddress: "3H8...",  # recipient address(showed only in case of crypto_withdrawal)
        #         "destinationMemoId": null      # recipient address's memo ID
        #     }
        #
        id = self.safe_string(transaction, 'id')
        txid = self.safe_string(transaction, 'txId')
        currencyId = self.safe_string(transaction, 'asset')
        code = self.safe_currency_code(currencyId, currency)
        type = self.safe_string(transaction, 'type')
        if (type == 'crypto_withdrawal') or (type == 'fiat_withdrawal'):
            type = 'withdrawal'
        elif (type == 'crypto_deposit' or type == 'fiat_deposit'):
            type = 'deposit'
        amount = self.safe_float(transaction, 'netAmount')
        feeCost = self.safe_float(transaction, 'feeAmount')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': code,
            }
        timestamp = self.safe_timestamp(transaction, 'reviewStartedAt')
        updated = self.safe_timestamp(transaction, 'completedAt')
        addressFrom = self.safe_string(transaction, 'sourceAddress')
        addressTo = self.safe_string(transaction, 'destinationAddress')
        tagFrom = self.safe_string(transaction, 'sourceMemoId')
        tagTo = self.safe_string(transaction, 'destinationMemoId')
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'addressFrom': addressFrom,
            'address': addressTo,
            'addressTo': addressTo,
            'tagFrom': tagFrom,
            'tag': tagTo,
            'tagTo': tagTo,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'comment': None,
            'fee': fee,
        }

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'limit': limit,  # max 20
            # 'latestmin': limit,  # read data older than self id
            # 'after': self.milliseconds(),
            # 'before': since,
            # 'completedOnly': 'no',
        }
        if since is not None:
            request['before'] = since
        if limit is not None:
            request['limit'] = limit
        response = self.privateGetDepositWithdrawalStatus(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": 640,                     # deposit/withdrawal event ID
        #             "asset": "BTC",                # asset name
        #             "type": "crypto_withdrawal",   # fiat_withdrawal, fiat_deposit, crypto_withdrawal, crypto_deposit
        #             "netAmount": 0.0001,           # amount
        #             "feeAmount": 0.0005,           # fee(null if there is no imposed fee)
        #             "status": "completed",         # reviewing, rejected, processing, failed, completed
        #             "reviewStartedAt": 1595556218,  # request time
        #             "completedAt": 1595556902,     # completion time(showed only in case of completed)
        #             "txId": "eaca5ad3...",         # tx ID
        #             "sourceAddress": null,         # sender address(showed only in case of crypto_deposit)
        #             "destinationAddress: "3H8...",  # recipient address(showed only in case of crypto_withdrawal)
        #             "destinationMemoId": null      # recipient address's memo ID
        #         },
        #     ]
        #
        currency = None
        if code is not None:
            currency = self.currency(code)
        return self.parse_transactions(response, currency, since, limit, params)

    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_params(self.urls['api'][api], {'hostname': self.hostname}) + endpoint
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        elif api == 'private':
            self.check_required_credentials()
            timestamp = str(self.nonce())
            auth = 't' + timestamp + method + endpoint
            headers = {
                'api-key': self.apiKey,
                'timestamp': timestamp,
            }
            if method == 'POST':
                headers['Content-Type'] = 'application/json'
                body = self.json(params)
                auth += body
            elif endpoint == '/orders':
                if query:
                    urlQuery = '?' + self.urlencode(query)
                    auth += urlQuery
                    url += urlQuery
            elif method == 'GET':
                if query:
                    url += '?' + self.urlencode(query)
            rawSecret = self.base64_to_binary(self.secret)
            signature = self.hmac(self.encode(auth), rawSecret, hashlib.sha512, 'base64')
            headers['signature'] = signature
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        #
        #     {"errorMessage":"Invalid API key","errorCode":10155}
        #
        if not isinstance(response, list):
            errorCode = self.safe_string(response, 'errorCode')
            errorMessage = self.safe_string(response, 'errorMessage')
            feedback = self.id + ' ' + body
            if errorMessage is not None:
                self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
            if errorCode is not None:
                self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            if (errorCode is not None) or (errorMessage is not None):
                raise ExchangeError(feedback)
