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

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

from ccxt.async_support.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import AddressPending
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import ROUND
from ccxt.base.precise import Precise


class vcc(Exchange):

    def describe(self):
        return self.deep_extend(super(vcc, self).describe(), {
            'id': 'vcc',
            'name': 'VCC Exchange',
            'countries': ['VN'],  # Vietnam
            'rateLimit': 1000,
            'version': 'v3',
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createOrder': True,
                'editOrder': False,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchTicker': 'emulated',
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFees': False,
                'fetchTransactions': True,
                'fetchWithdrawals': True,
            },
            'timeframes': {
                '1m': '60000',
                '5m': '300000',
                '15m': '900000',
                '30m': '1800000',
                '1h': '3600000',
                '2h': '7200000',
                '4h': '14400000',
                '6h': '21600000',
                '12h': '43200000',
                '1d': '86400000',
                '1w': '604800000',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/100545356-8427f500-326c-11eb-9539-7d338242d61b.jpg',
                'api': {
                    'public': 'https://api.vcc.exchange',
                    'private': 'https://api.vcc.exchange',
                },
                'www': 'https://vcc.exchange',
                'doc': [
                    'https://vcc.exchange/api',
                ],
                'fees': 'https://support.vcc.exchange/hc/en-us/articles/360016401754',
                'referral': 'https://vcc.exchange?ref=l4xhrH',
            },
            'api': {
                'public': {
                    'get': [
                        'summary',
                        'exchange_info',
                        'assets',  # Available Currencies
                        'ticker',  # Ticker list for all symbols
                        'trades/{market_pair}',  # Recent trades
                        'orderbook/{market_pair}',  # Orderbook
                        'chart/bars',  # Candles
                        'tick_sizes',
                    ],
                },
                'private': {
                    'get': [
                        'user',
                        'balance',  # Get trading balance
                        'orders/{order_id}',  # Get a single order by order_id
                        'orders/open',  # Get open orders
                        'orders',  # Get closed orders
                        'orders/trades',  # Get trades history
                        'deposit-address',  # Generate or get deposit address
                        'transactions',  # Get deposit/withdrawal history
                    ],
                    'post': [
                        'orders',  # Create new order
                    ],
                    'put': [
                        'orders/{order_id}/cancel',  # Cancel order
                        'orders/cancel-by-type',
                        'orders/cancel-all',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.2 / 100,
                    'taker': 0.2 / 100,
                },
            },
            'exceptions': {
                'exact': {},
                'broad': {
                    'limit may not be greater than': BadRequest,  # {"message":"The given data was invalid.","errors":{"limit":["The limit may not be greater than 1000."]}}
                    'Insufficient balance': InsufficientFunds,  # {"message":"Insufficient balance."}
                    'Unauthenticated': AuthenticationError,  # {"message":"Unauthenticated."}  # wrong api key
                    'signature is invalid': AuthenticationError,  # {"message":"The given data was invalid.","errors":{"signature":["HMAC signature is invalid"]}}
                    'Timeout': RequestTimeout,  # {"code":504,"message":"Gateway Timeout","description":""}
                    'Too many requests': RateLimitExceeded,  # {"code":429,"message":"Too many requests","description":"Too many requests"}
                    'quantity field is required': InvalidOrder,  # {"message":"The given data was invalid.","errors":{"quantity":["The quantity field is required when type is market."]}}
                    'price field is required': InvalidOrder,  # {"message":"The given data was invalid.","errors":{"price":["The price field is required when type is limit."]}}
                    'error_security_level': PermissionDenied,  # {"message":"error_security_level"}
                    'pair is invalid': BadSymbol,  # {"message":"The given data was invalid.","errors":{"coin":["Trading pair is invalid","Trading pair is offline"]}}
                    # {"message":"The given data was invalid.","errors":{"type":["The selected type is invalid."]}}
                    # {"message":"The given data was invalid.","errors":{"trade_type":["The selected trade type is invalid."]}}
                    'type is invalid': InvalidOrder,
                    'Data not found': OrderNotFound,  # {"message":"Data not found"}
                },
            },
        })

    async def fetch_markets(self, params={}):
        response = await self.publicGetExchangeInfo(params)
        #
        #     {
        #         "message":null,
        #         "dataVersion":"4677e56a42f0c29872f3a6e75f5d39d2f07c748c",
        #         "data":{
        #             "timezone":"UTC",
        #             "serverTime":1605821914333,
        #             "symbols":[
        #                 {
        #                     "id":"btcvnd",
        #                     "symbol":"BTC\/VND",
        #                     "coin":"btc",
        #                     "currency":"vnd",
        #                     "baseId":1,
        #                     "quoteId":0,
        #                     "active":true,
        #                     "base_precision":"0.0000010000",
        #                     "quote_precision":"1.0000000000",
        #                     "minimum_quantity":"0.0000010000",
        #                     "minimum_amount":"250000.0000000000",
        #                     "precision":{"price":0,"amount":6,"cost":6},
        #                     "limits":{
        #                         "amount":{"min":"0.0000010000"},
        #                         "price":{"min":"1.0000000000"},
        #                         "cost":{"min":"250000.0000000000"},
        #                     },
        #                 },
        #             ],
        #         },
        #     }
        #
        data = self.safe_value(response, 'data')
        markets = self.safe_value(data, 'symbols')
        result = []
        for i in range(0, len(markets)):
            market = self.safe_value(markets, i)
            symbol = self.safe_string(market, 'symbol')
            id = symbol.replace('/', '_')
            baseId = self.safe_string(market, 'coin')
            quoteId = self.safe_string(market, 'currency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            active = self.safe_value(market, 'active')
            precision = self.safe_value(market, 'precision', {})
            limits = self.safe_value(market, 'limits', {})
            amountLimits = self.safe_value(limits, 'amount', {})
            priceLimits = self.safe_value(limits, 'price', {})
            costLimits = self.safe_value(limits, 'cost', {})
            entry = {
                'info': market,
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'precision': {
                    'price': self.safe_integer(precision, 'price'),
                    'amount': self.safe_integer(precision, 'amount'),
                    'cost': self.safe_integer(precision, 'cost'),
                },
                'limits': {
                    'amount': {
                        'min': self.safe_number(amountLimits, 'min'),
                        'max': None,
                    },
                    'price': {
                        'min': self.safe_number(priceLimits, 'min'),
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_number(costLimits, 'min'),
                        'max': None,
                    },
                },
            }
            result.append(entry)
        return result

    async def fetch_currencies(self, params={}):
        response = await self.publicGetAssets(params)
        #
        #     {
        #         "message":null,
        #         "dataVersion":"2514c8012d94ea375018fc13e0b5d4d896e435df",
        #         "data":{
        #             "BTC":{
        #                 "name":"Bitcoin",
        #                 "unified_cryptoasset_id":1,
        #                 "can_withdraw":1,
        #                 "can_deposit":1,
        #                 "min_withdraw":"0.0011250000",
        #                 "max_withdraw":"100.0000000000",
        #                 "maker_fee":"0.002",
        #                 "taker_fee":"0.002",
        #                 "decimal":8,
        #                 "withdrawal_fee":"0.0006250000",
        #             },
        #         },
        #     }
        #
        result = {}
        data = self.safe_value(response, 'data')
        ids = list(data.keys())
        for i in range(0, len(ids)):
            id = self.safe_string_lower(ids, i)
            currency = self.safe_value(data, ids[i])
            code = self.safe_currency_code(id)
            canDeposit = self.safe_value(currency, 'can_deposit')
            canWithdraw = self.safe_value(currency, 'can_withdraw')
            active = (canDeposit and canWithdraw)
            result[code] = {
                'id': id,
                'code': code,
                'name': self.safe_string(currency, 'name'),
                'active': active,
                'fee': self.safe_number(currency, 'withdrawal_fee'),
                'precision': self.safe_integer(currency, 'decimal'),
                'limits': {
                    'withdraw': {
                        'min': self.safe_number(currency, 'min_withdraw'),
                        'max': self.safe_number(currency, 'max_withdraw'),
                    },
                },
            }
        return result

    async def fetch_trading_fee(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = self.extend({
            'symbol': market['id'],
        }, self.omit(params, 'symbol'))
        response = await self.privateGetTradingFeeSymbol(request)
        #
        #     {
        #         takeLiquidityRate: '0.001',
        #         provideLiquidityRate: '-0.0001'
        #     }
        #
        return {
            'info': response,
            'maker': self.safe_number(response, 'provideLiquidityRate'),
            'taker': self.safe_number(response, 'takeLiquidityRate'),
        }

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetBalance(params)
        #
        #     {
        #         "message":null,
        #         "dataVersion":"7168e6c99e90f60673070944d987988eef7d91fa",
        #         "data":{
        #             "vnd":{"balance":0,"available_balance":0},
        #             "btc":{"balance":0,"available_balance":0},
        #             "eth":{"balance":0,"available_balance":0},
        #         },
        #     }
        #
        data = self.safe_value(response, 'data')
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        currencyIds = list(data.keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            code = self.safe_currency_code(currencyId)
            balance = self.safe_value(data, currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'available_balance')
            account['total'] = self.safe_string(balance, 'balance')
            result[code] = account
        return self.parse_balance(result, False)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "low":"415805323.0000000000",
        #         "high":"415805323.0000000000",
        #         "open":"415805323.0000000000",
        #         "close":"415805323.0000000000",
        #         "time":"1605845940000",
        #         "volume":"0.0065930000",
        #         "opening_time":1605845963263,
        #         "closing_time":1605845963263
        #     }
        #
        return [
            self.safe_integer(ohlcv, 'time'),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'volume'),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'coin': market['baseId'],
            'currency': market['quoteId'],
            'resolution': self.timeframes[timeframe],
        }
        limit = 100 if (limit is None) else limit
        limit = min(100, limit)
        duration = self.parse_timeframe(timeframe)
        if since is None:
            end = self.seconds()
            request['to'] = end
            request['from'] = end - limit * duration
        else:
            start = int(since / 1000)
            request['from'] = start
            request['to'] = self.sum(start, limit * duration)
        response = await self.publicGetChartBars(self.extend(request, params))
        #
        #     [
        #         {"low":"415805323.0000000000","high":"415805323.0000000000","open":"415805323.0000000000","close":"415805323.0000000000","time":"1605845940000","volume":"0.0065930000","opening_time":1605845963263,"closing_time":1605845963263},
        #         {"low":"416344148.0000000000","high":"416344148.0000000000","open":"415805323.0000000000","close":"416344148.0000000000","time":"1605846000000","volume":"0.0052810000","opening_time":1605846011490,"closing_time":1605846011490},
        #         {"low":"416299269.0000000000","high":"417278376.0000000000","open":"416344148.0000000000","close":"417278376.0000000000","time":"1605846060000","volume":"0.0136750000","opening_time":1605846070727,"closing_time":1605846102282},
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_pair': market['id'],
            # 'depth': 0,  # 0 = full orderbook, 5, 10, 20, 50, 100, 500
            'level': 2,  # 1 = best bidask, 2 = aggregated by price, 3 = no aggregation
        }
        if limit is not None:
            if (limit != 0) and (limit != 5) and (limit != 10) and (limit != 20) and (limit != 50) and (limit != 100) and (limit != 500):
                raise BadRequest(self.id + ' fetchOrderBook limit must be 0, 5, 10, 20, 50, 100, 500 if specified')
            request['depth'] = limit
        response = await self.publicGetOrderbookMarketPair(self.extend(request, params))
        #
        #     {
        #         "message":null,
        #         "dataVersion":"376cee43af26deabcd3762ab11a876b6e7a71e82",
        #         "data":{
        #             "bids":[
        #                 ["413342637.0000000000","0.165089"],
        #                 ["413274576.0000000000","0.03"],
        #                 ["413274574.0000000000","0.03"],
        #             ],
        #             "asks":[
        #                 ["416979125.0000000000","0.122835"],
        #                 ["417248934.0000000000","0.030006"],
        #                 ["417458879.0000000000","0.1517"],
        #             ],
        #             "timestamp":"1605841619147"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        timestamp = self.safe_value(data, 'timestamp')
        return self.parse_order_book(data, symbol, timestamp, 'bids', 'asks', 0, 1)

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "base_id":1,
        #         "quote_id":0,
        #         "last_price":"411119457",
        #         "max_price":"419893173.0000000000",
        #         "min_price":"401292577.0000000000",
        #         "open_price":null,
        #         "base_volume":"10.5915050000",
        #         "quote_volume":"4367495977.4484430060",
        #         "isFrozen":0
        #     }
        #
        timestamp = self.milliseconds()
        baseVolume = self.safe_number(ticker, 'base_volume')
        quoteVolume = self.safe_number(ticker, 'quote_volume')
        open = self.safe_number(ticker, 'open_price')
        last = self.safe_number(ticker, 'last_price')
        change = None
        percentage = None
        average = None
        if last is not None and open is not None:
            change = last - open
            average = self.sum(last, open) / 2
            if open > 0:
                percentage = change / open * 100
        vwap = self.vwap(baseVolume, quoteVolume)
        symbol = None if (market is None) else market['symbol']
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'max_price'),
            'low': self.safe_number(ticker, 'min_price'),
            'bid': self.safe_number(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_number(ticker, 'ask'),
            'askVolume': None,
            'vwap': vwap,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        response = await self.publicGetTicker(params)
        #
        #     {
        #         "message":null,
        #         "dataVersion":"fc521161aebe506178b8588cd2adb598eaf1018e",
        #         "data":{
        #             "BTC_VND":{
        #                 "base_id":1,
        #                 "quote_id":0,
        #                 "last_price":"411119457",
        #                 "max_price":"419893173.0000000000",
        #                 "min_price":"401292577.0000000000",
        #                 "open_price":null,
        #                 "base_volume":"10.5915050000",
        #                 "quote_volume":"4367495977.4484430060",
        #                 "isFrozen":0
        #             },
        #         }
        #     }
        #
        result = {}
        data = self.safe_value(response, 'data')
        marketIds = list(data.keys())
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            market = self.safe_market(marketId, None, '_')
            symbol = market['symbol']
            result[symbol] = self.parse_ticker(data[marketId], market)
        return self.filter_by_array(result, 'symbol', symbols)

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #     {
        #         "trade_id":181509285,
        #         "price":"415933022.0000000000",
        #         "base_volume":"0.0022080000",
        #         "quote_volume":"918380.1125760000",
        #         "trade_timestamp":1605842150357,
        #         "type":"buy",
        #     }
        #
        # private fetchMyTrades
        #
        #     {
        #         "trade_type":"sell",
        #         "fee":"0.0610578086",
        #         "id":1483372,
        #         "created_at":1606581578368,
        #         "currency":"usdt",
        #         "coin":"btc",
        #         "price":"17667.1900000000",
        #         "quantity":"0.0017280000",
        #         "amount":"30.5289043200",
        #     }
        #
        timestamp = self.safe_integer_2(trade, 'trade_timestamp', 'created_at')
        baseId = self.safe_string_upper(trade, 'coin')
        quoteId = self.safe_string_upper(trade, 'currency')
        marketId = None
        if (baseId is not None) and (quoteId is not None):
            marketId = baseId + '_' + quoteId
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string_2(trade, 'base_volume', 'quantity')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.safe_number_2(trade, 'quote_volume', 'amount')
        if cost is None:
            cost = self.parse_number(Precise.string_mul(priceString, amountString))
        side = self.safe_string_2(trade, 'type', 'trade_type')
        id = self.safe_string_2(trade, 'trade_id', 'id')
        feeCost = self.safe_number(trade, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': market['quote'],
            }
        return {
            'info': trade,
            'id': id,
            'order': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_pair': market['id'],
            # 'type': 'buy',  # 'sell'
            # 'count': limit,  # default 500, max 1000
        }
        if limit is not None:
            request['count'] = min(1000, limit)
        response = await self.publicGetTradesMarketPair(self.extend(request, params))
        #
        #     {
        #         "message":null,
        #         "dataVersion":"1f811b533143f739008a3e4ecaaab2ec82ea50d4",
        #         "data":[
        #             {
        #                 "trade_id":181509285,
        #                 "price":"415933022.0000000000",
        #                 "base_volume":"0.0022080000",
        #                 "quote_volume":"918380.1125760000",
        #                 "trade_timestamp":1605842150357,
        #                 "type":"buy",
        #             },
        #         ],
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_trades(data, market, since, limit)

    async def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'type': type,  # 'deposit', 'withdraw'
            # 'start': int(since / 1000),
            # 'end': self.seconds(),
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if limit is not None:
            request['limit'] = min(1000, limit)
        if since is not None:
            request['start'] = since
        response = await self.privateGetTransactions(self.extend(request, params))
        #
        #     {
        #         "message":null,
        #         "dataVersion":"1fdfb0ec85b666871d62fe59d098d01839b05e97",
        #         "data":{
        #             "current_page":1,
        #             "data":[
        #                 {
        #                     "id":85391,
        #                     "user_id":253063,
        #                     "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131",
        #                     "internal_transaction_id":null,
        #                     "temp_transaction_id":"2367",
        #                     "currency":"usdt",
        #                     "amount":"30.0000000000",
        #                     "btc_amount":"0.0000000000",
        #                     "usdt_amount":"0.0000000000",
        #                     "fee":"0.0000000000",
        #                     "tx_cost":"0.0000000000",
        #                     "confirmation":0,
        #                     "deposit_code":null,
        #                     "status":"success",
        #                     "bank_name":null,
        #                     "foreign_bank_account":null,
        #                     "foreign_bank_account_holder":null,
        #                     "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44",
        #                     "destination_tag":null,
        #                     "error_detail":null,
        #                     "refunded":"0.0000000000",
        #                     "transaction_date":"2020-11-28",
        #                     "transaction_timestamp":"1606563143.959",
        #                     "created_at":1606563143959,
        #                     "updated_at":1606563143959,
        #                     "transaction_email_timestamp":0,
        #                     "network":null,
        #                     "collect_tx_id":null,
        #                     "collect_id":null
        #                 }
        #             ],
        #             "first_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1",
        #             "from":1,
        #             "last_page":1,
        #             "last_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1",
        #             "next_page_url":null,
        #             "path":"http:\/\/api.vcc.exchange\/v3\/transactions",
        #             "per_page":10,
        #             "prev_page_url":null,
        #             "to":1,
        #             "total":1
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        data = self.safe_value(data, 'data', [])
        return self.parse_transactions(data, currency, since, limit)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        request = {'type': 'deposit'}
        return await self.fetch_transactions(code, since, limit, self.extend(request, params))

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        request = {'type': 'withdraw'}
        return await self.fetch_transactions(code, since, limit, self.extend(request, params))

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchTransactions, fetchDeposits, fetchWithdrawals
        #
        #     {
        #         "id":85391,
        #         "user_id":253063,
        #         "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131",
        #         "internal_transaction_id":null,
        #         "temp_transaction_id":"2367",
        #         "currency":"usdt",
        #         "amount":"30.0000000000",
        #         "btc_amount":"0.0000000000",
        #         "usdt_amount":"0.0000000000",
        #         "fee":"0.0000000000",
        #         "tx_cost":"0.0000000000",
        #         "confirmation":0,
        #         "deposit_code":null,
        #         "status":"success",
        #         "bank_name":null,
        #         "foreign_bank_account":null,
        #         "foreign_bank_account_holder":null,
        #         "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44",
        #         "destination_tag":null,
        #         "error_detail":null,
        #         "refunded":"0.0000000000",
        #         "transaction_date":"2020-11-28",
        #         "transaction_timestamp":"1606563143.959",
        #         "created_at":1606563143959,
        #         "updated_at":1606563143959,
        #         "transaction_email_timestamp":0,
        #         "network":null,
        #         "collect_tx_id":null,
        #         "collect_id":null
        #     }
        #
        id = self.safe_string(transaction, 'id')
        timestamp = self.safe_integer(transaction, 'created_at')
        updated = self.safe_integer(transaction, 'updated_at')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        amount = self.safe_number(transaction, 'amount')
        if amount is not None:
            amount = abs(amount)
        address = self.safe_string(transaction, 'blockchain_address')
        txid = self.safe_string(transaction, 'transaction_id')
        tag = self.safe_string(transaction, 'destination_tag')
        fee = None
        feeCost = self.safe_number(transaction, 'fee')
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': code,
            }
        type = amount > 'deposit' if 0 else 'withdrawal'
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'addressTo': address,
            'addressFrom': None,
            'tag': tag,
            'tagTo': tag,
            'tagFrom': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    def parse_transaction_status(self, status):
        statuses = {
            'pending': 'pending',
            'error': 'failed',
            'success': 'ok',
            'cancel': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction_type(self, type):
        types = {
            'deposit': 'deposit',
            'withdraw': 'withdrawal',
        }
        return self.safe_string(types, type, type)

    def cost_to_precision(self, symbol, cost):
        return self.decimal_to_precision(cost, ROUND, self.markets[symbol]['precision']['cost'], self.precisionMode, self.paddingMode)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'coin': market['baseId'],
            'currency': market['quoteId'],
            'trade_type': side,
            'type': type,
        }
        if type == 'ceiling_market':
            ceiling = self.safe_value(params, 'ceiling')
            if ceiling is not None:
                request['ceiling'] = self.cost_to_precision(symbol, ceiling)
            elif price is not None:
                request['ceiling'] = self.cost_to_precision(symbol, amount * price)
            else:
                raise InvalidOrder(self.id + ' createOrder() requires a price argument or a ceiling parameter for ' + type + ' orders')
        else:
            request['quantity'] = self.amount_to_precision(symbol, amount)
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
        stopPrice = self.safe_value_2(params, 'stop_price', 'stopPrice')
        if stopPrice is not None:
            request['is_stop'] = 1
            request['stop_condition'] = 'le' if (side == 'buy') else 'ge'  # ge = greater than or equal, le = less than or equal
            request['stop_price'] = self.price_to_precision(symbol, price)
        params = self.omit(params, ['stop_price', 'stopPrice'])
        response = await self.privatePostOrders(self.extend(request, params))
        #
        # ceiling_market order
        #
        #     {
        #         "message":null,
        #         "dataVersion":"213fc0d433f38307f736cae1cbda4cc310469b7a",
        #         "data":{
        #             "coin":"btc",
        #             "currency":"usdt",
        #             "trade_type":"buy",
        #             "type":"ceiling_market",
        #             "ceiling":"30",
        #             "user_id":253063,
        #             "email":"igor.kroitor@gmail.com",
        #             "side":"buy",
        #             "quantity":"0.00172800",
        #             "status":"pending",
        #             "fee":0,
        #             "created_at":1606571333035,
        #             "updated_at":1606571333035,
        #             "instrument_symbol":"BTCUSDT",
        #             "remaining":"0.00172800",
        #             "fee_rate":"0.002",
        #             "id":88214435
        #         }
        #     }
        #
        # limit order
        #
        #     {
        #         "message":null,
        #         "dataVersion":"d9b1159d2bcefa2388be156e32ddc7cc324400ee",
        #         "data":{
        #             "id":41230,
        #             "trade_type":"sell",
        #             "type":"limit",
        #             "quantity":"1",
        #             "price":"14.99",
        #             "currency":"usdt",
        #             "coin":"neo",
        #             "status":"pending",
        #             "is_stop": "1",
        #             "stop_price": "13",
        #             "stop_condition": "ge",
        #             "fee":0,
        #             "created_at":1560244052168,
        #             "updated_at":1560244052168
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_order(data, market)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'order_id': id,
        }
        response = await self.privatePutOrdersOrderIdCancel(self.extend(request, params))
        return self.parse_order(response)

    async def cancel_all_orders(self, symbol=None, params={}):
        type = self.safe_string(params, 'type')
        method = 'privatePutOrdersCancelAll' if (type is None) else 'privatePutOrdersCancelByType'
        request = {}
        if type is not None:
            request['type'] = type
        await self.load_markets()
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff",
        #         "data":{
        #             "data":[
        #                 {
        #                     "id":410,
        #                     "trade_type":"sell",
        #                     "currency":"usdt",
        #                     "coin":"neo",
        #                     "type":"limit",
        #                     "quantity":"1.0000000000",
        #                     "price":"14.9900000000",
        #                     "executed_quantity":"0.0000000000",
        #                     "executed_price":"0.0000000000",
        #                     "fee":"0.0000000000",
        #                     "status":"canceled",
        #                     "created_at":1560244052168,
        #                     "updated_at":1560244052168,
        #                 },
        #             ],
        #         },
        #     }
        #
        data = self.safe_value(response, 'data', {})
        data = self.safe_value(response, 'data', [])
        return self.parse_orders(data)

    def parse_order_status(self, status):
        statuses = {
            'pending': 'open',
            'stopping': 'open',
            'executing': 'open',
            'executed': 'closed',
            'canceled': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # ceiling_market
        #
        #     {
        #         "coin":"btc",
        #         "currency":"usdt",
        #         "trade_type":"buy",
        #         "type":"ceiling_market",
        #         "ceiling":"30",
        #         "user_id":253063,
        #         "email":"igor.kroitor@gmail.com",
        #         "side":"buy",
        #         "quantity":"0.00172800",
        #         "status":"pending",
        #         "fee":0,
        #         "created_at":1606571333035,
        #         "updated_at":1606571333035,
        #         "instrument_symbol":"BTCUSDT",
        #         "remaining":"0.00172800",
        #         "fee_rate":"0.002",
        #         "id":88214435
        #     }
        #
        # limit order
        #
        #     {
        #         "id":41230,
        #         "trade_type":"sell",
        #         "type":"limit",
        #         "quantity":"1",
        #         "price":"14.99",
        #         "currency":"usdt",
        #         "coin":"neo",
        #         "status":"pending",
        #         "is_stop": "1",
        #         "stop_price": "13",
        #         "stop_condition": "ge",
        #         "fee":0,
        #         "created_at":1560244052168,
        #         "updated_at":1560244052168
        #     }
        #
        created = self.safe_value(order, 'created_at')
        updated = self.safe_value(order, 'updated_at')
        baseId = self.safe_string_upper(order, 'coin')
        quoteId = self.safe_string_upper(order, 'currency')
        marketId = baseId + '_' + quoteId
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        amount = self.safe_number(order, 'quantity')
        filled = self.safe_number(order, 'executed_quantity')
        status = self.parse_order_status(self.safe_string(order, 'status'))
        cost = self.safe_number(order, 'ceiling')
        id = self.safe_string(order, 'id')
        price = self.safe_number(order, 'price')
        average = self.safe_number(order, 'executed_price')
        remaining = self.safe_number(order, 'remaining')
        type = self.safe_string(order, 'type')
        side = self.safe_string(order, 'trade_type')
        fee = {
            'currency': market['quote'],
            'cost': self.safe_number(order, 'fee'),
            'rate': self.safe_number(order, 'fee_rate'),
        }
        lastTradeTimestamp = None
        if updated != created:
            lastTradeTimestamp = updated
        stopPrice = self.safe_number(order, 'stopPrice')
        return self.safe_order({
            'id': id,
            'clientOrderId': id,
            'timestamp': created,
            'datetime': self.iso8601(created),
            'lastTradeTimestamp': lastTradeTimestamp,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'average': average,
            'amount': amount,
            'cost': cost,
            'filled': filled,
            'remaining': remaining,
            'fee': fee,
            'trades': None,
            'info': order,
        })

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'order_id': id,
        }
        response = await self.privateGetOrdersOrderId(self.extend(request, params))
        #
        #     {
        #         "message":null,
        #         "dataVersion":"57448aa1fb8f227254e8e2e925b3ade8e1e5bbef",
        #         "data":{
        #             "id":88265741,
        #             "user_id":253063,
        #             "email":"igor.kroitor@gmail.com",
        #             "updated_at":1606581578141,
        #             "created_at":1606581578141,
        #             "coin":"btc",
        #             "currency":"usdt",
        #             "type":"market",
        #             "trade_type":"sell",
        #             "executed_price":"17667.1900000000",
        #             "price":null,
        #             "executed_quantity":"0.0017280000",
        #             "quantity":"0.0017280000",
        #             "fee":"0.0610578086",
        #             "status":"executed",
        #             "is_stop":0,
        #             "stop_condition":null,
        #             "stop_price":null,
        #             "ceiling":null
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_order(data)

    async def fetch_orders_with_method(self, method, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'page': 1,
            # 'limit': limit,  # max 1000
            # 'start_date': since,
            # 'end_date': self.milliseconds(),
            # 'currency': market['quoteId'],
            # 'coin': market['baseId'],
            # 'trade_type': 'buy',  # or 'sell'
            # 'hide_canceled': 0,  # 1 to exclude canceled orders
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['coin'] = market['baseId']
            request['currency'] = market['quoteId']
        if since is not None:
            request['start_date'] = since
        if limit is not None:
            request['limit'] = min(1000, limit)  # max 1000
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "message":null,
        #         "dataVersion":"89aa11497f23fdd34cf9de9c55acfad863c78780",
        #         "data":{
        #             "current_page":1,
        #             "data":[
        #                 {
        #                     "id":88489678,
        #                     "email":"igor.kroitor@gmail.com",
        #                     "updated_at":1606628593567,
        #                     "created_at":1606628593567,
        #                     "coin":"btc",
        #                     "currency":"usdt",
        #                     "type":"limit",
        #                     "trade_type":"buy",
        #                     "executed_price":"0.0000000000",
        #                     "price":"10000.0000000000",
        #                     "executed_quantity":"0.0000000000",
        #                     "quantity":"0.0010000000",
        #                     "fee":"0.0000000000",
        #                     "status":"pending",
        #                     "is_stop":0,
        #                     "stop_condition":null,
        #                     "stop_price":null,
        #                     "ceiling":null,
        #                 },
        #             ],
        #             "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1",
        #             "from":1,
        #             "last_page":1,
        #             "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1",
        #             "next_page_url":null,
        #             "path":"http:\/\/api.vcc.exchange\/v3\/orders\/open",
        #             "per_page":10,
        #             "prev_page_url":null,
        #             "to":1,
        #             "total":1,
        #         },
        #     }
        #
        data = self.safe_value(response, 'data', {})
        data = self.safe_value(data, 'data', [])
        return self.parse_orders(data, market, since, limit)

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

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

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'page': 1,
            # 'limit': limit,  # max 1000
            # 'start_date': since,
            # 'end_date': self.milliseconds(),
            # 'currency': market['quoteId'],
            # 'coin': market['baseId'],
            # 'trade_type': 'buy',  # or 'sell'
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['coin'] = market['baseId']
            request['currency'] = market['quoteId']
        if since is not None:
            request['start_date'] = since
        if limit is not None:
            request['limit'] = min(1000, limit)  # max 1000
        response = await self.privateGetOrdersTrades(self.extend(request, params))
        #
        #     {
        #         "message":null,
        #         "dataVersion":"eb890af684cf84e20044e9a9771b96302e7b8dec",
        #         "data":{
        #             "current_page":1,
        #             "data":[
        #                 {
        #                     "trade_type":"sell",
        #                     "fee":"0.0610578086",
        #                     "id":1483372,
        #                     "created_at":1606581578368,
        #                     "currency":"usdt",
        #                     "coin":"btc",
        #                     "price":"17667.1900000000",
        #                     "quantity":"0.0017280000",
        #                     "amount":"30.5289043200",
        #                 },
        #             ],
        #             "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1",
        #             "from":1,
        #             "last_page":1,
        #             "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1",
        #             "next_page_url":null,
        #             "path":"http:\/\/api.vcc.exchange\/v3\/orders\/trades",
        #             "per_page":10,
        #             "prev_page_url":null,
        #             "to":2,
        #             "total":2,
        #         },
        #     }
        #
        data = self.safe_value(response, 'data', {})
        data = self.safe_value(data, 'data', [])
        return self.parse_trades(data, market, since, limit)

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await self.privateGetDepositAddress(self.extend(request, params))
        #
        #     {
        #         "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff",
        #         "data":{
        #             "status": "REQUESTED",
        #             "blockchain_address": "",
        #             "currency": "btc"
        #         }
        #     }
        #
        #     {
        #         "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff",
        #         "data":{
        #             "status": "PROVISIONED",
        #             "blockchain_address": "rPVMhWBsfF9iMXYj3aAzJVkPDTFNSyWdKy",
        #             "blockchain_tag": "920396135",
        #             "currency": "xrp"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        status = self.safe_string(data, 'status')
        if status == 'REQUESTED':
            raise AddressPending(self.id + ' is generating ' + code + ' deposit address, call fetchDepositAddress one more time later to retrieve the generated address')
        address = self.safe_string(data, 'blockchain_address')
        self.check_address(address)
        tag = self.safe_string(data, 'blockchain_tag')
        currencyId = self.safe_string(data, 'currency')
        return {
            'currency': self.safe_currency_code(currencyId),
            'address': address,
            'tag': tag,
            'info': data,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if query:
            url += '?' + self.urlencode(query)
        if api == 'private':
            self.check_required_credentials()
            timestamp = str(self.milliseconds())
            if method != 'GET':
                body = self.json(query)
            auth = method + ' ' + url
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
            headers = {
                'Authorization': 'Bearer ' + self.apiKey,
                'Content-Type': 'application/json',
                'timestamp': timestamp,
                'signature': signature,
            }
        url = self.urls['api'][api] + '/' + url
        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
        #
        #     {"message":"Insufficient balance."}
        #     {"message":"Unauthenticated."}  # wrong api key
        #     {"message":"The given data was invalid.","errors":{"signature":["HMAC signature is invalid"]}}
        #     {"code":504,"message":"Gateway Timeout","description":""}
        #     {"code":429,"message":"Too many requests","description":"Too many requests"}
        #
        message = self.safe_string(response, 'message')
        if message is not None:
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
            raise ExchangeError(feedback)
