# -*- 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
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import BadResponse
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.errors import NetworkError
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import InvalidNonce


class digifinex(Exchange):

    def describe(self):
        return self.deep_extend(super(digifinex, self).describe(), {
            'id': 'digifinex',
            'name': 'DigiFinex',
            'countries': ['SG'],
            'version': 'v3',
            'rateLimit': 900,  # 300 for posts
            'has': {
                'cancelOrder': True,
                'cancelOrders': True,
                'createOrder': True,
                'fetchBalance': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1',
                '5m': '5',
                '15m': '15',
                '30m': '30',
                '1h': '60',
                '4h': '240',
                '12h': '720',
                '1d': '1D',
                '1w': '1W',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87443315-01283a00-c5fe-11ea-8628-c2a0feaf07ac.jpg',
                'api': 'https://openapi.digifinex.com',
                'www': 'https://www.digifinex.com',
                'doc': [
                    'https://docs.digifinex.com',
                ],
                'fees': 'https://digifinex.zendesk.com/hc/en-us/articles/360000328422-Fee-Structure-on-DigiFinex',
                'referral': 'https://www.digifinex.com/en-ww/from/DhOzBg?channelCode=ljaUPp',
            },
            'api': {
                'v2': {
                    'get': [
                        'ticker',
                    ],
                },
                'public': {
                    'get': [
                        '{market}/symbols',
                        'kline',
                        'margin/currencies',
                        'margin/symbols',
                        'markets',
                        'order_book',
                        'ping',
                        'spot/symbols',
                        'time',
                        'trades',
                        'trades/symbols',
                        'ticker',
                        'currencies',  # todo add fetchCurrencies
                    ],
                },
                'private': {
                    'get': [
                        '{market}/financelog',
                        '{market}/mytrades',
                        '{market}/order',
                        '{market}​/order​/detail',  # todo add fetchOrder
                        '{market}/order/current',
                        '{market}/order/history',
                        'margin/assets',
                        'margin/financelog',
                        'margin/mytrades',
                        'margin/order',
                        'margin/order/current',
                        'margin/order/history',
                        'margin/positions',
                        'otc/financelog',
                        'spot/assets',
                        'spot/financelog',
                        'spot/mytrades',
                        'spot/order',
                        'spot/order/current',
                        'spot/order/history',
                        'deposit/address',  # todo add fetchDepositAddress
                        'deposit/history',  # todo add fetchDeposits
                        'withdraw/history',  # todo add fetchWithdrawals
                    ],
                    'post': [
                        '{market}/order/cancel',
                        '{market}/order/new',
                        '{market}​/order​/batch_new',
                        'margin/order/cancel',
                        'margin/order/new',
                        'margin/position/close',
                        'spot/order/cancel',
                        'spot/order/new',
                        'transfer',
                        'withdraw/new',  # todo add withdraw()
                        'withdraw/cancel',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.002,
                    'taker': 0.002,
                },
            },
            'exceptions': {
                'exact': {
                    '10001': [BadRequest, "Wrong request method, please check it's a GET ot POST request"],
                    '10002': [AuthenticationError, 'Invalid ApiKey'],
                    '10003': [AuthenticationError, "Sign doesn't match"],
                    '10004': [BadRequest, 'Illegal request parameters'],
                    '10005': [DDoSProtection, 'Request frequency exceeds the limit'],
                    '10006': [PermissionDenied, 'Unauthorized to execute self request'],
                    '10007': [PermissionDenied, 'IP address Unauthorized'],
                    '10008': [InvalidNonce, 'Timestamp for self request is invalid, timestamp must within 1 minute'],
                    '10009': [NetworkError, 'Unexist endpoint, please check endpoint URL'],
                    '10011': [AccountSuspended, 'ApiKey expired. Please go to client side to re-create an ApiKey'],
                    '20001': [PermissionDenied, 'Trade is not open for self trading pair'],
                    '20002': [PermissionDenied, 'Trade of self trading pair is suspended'],
                    '20003': [InvalidOrder, 'Invalid price or amount'],
                    '20007': [InvalidOrder, 'Price precision error'],
                    '20008': [InvalidOrder, 'Amount precision error'],
                    '20009': [InvalidOrder, 'Amount is less than the minimum requirement'],
                    '20010': [InvalidOrder, 'Cash Amount is less than the minimum requirement'],
                    '20011': [InsufficientFunds, 'Insufficient balance'],
                    '20012': [BadRequest, 'Invalid trade type, valid value: buy/sell)'],
                    '20013': [InvalidOrder, 'No order info found'],
                    '20014': [BadRequest, 'Invalid date, Valid format: 2018-07-25)'],
                    '20015': [BadRequest, 'Date exceeds the limit'],
                    '20018': [PermissionDenied, 'Your trading rights have been banned by the system'],
                    '20019': [BadRequest, 'Wrong trading pair symbol. Correct format:"usdt_btc". Quote asset is in the front'],
                    '20020': [DDoSProtection, "You have violated the API operation trading rules and temporarily forbid trading. At present, we have certain restrictions on the user's transaction rate and withdrawal rate."],
                    '50000': [ExchangeError, 'Exception error'],
                    '20021': [BadRequest, 'Invalid currency'],
                    '20022': [BadRequest, 'The ending timestamp must be larger than the starting timestamp'],
                    '20023': [BadRequest, 'Invalid transfer type'],
                    '20024': [BadRequest, 'Invalid amount'],
                    '20025': [BadRequest, 'This currency is not transferable at the moment'],
                    '20026': [InsufficientFunds, 'Transfer amount exceed your balance'],
                    '20027': [PermissionDenied, 'Abnormal account status'],
                    '20028': [PermissionDenied, 'Blacklist for transfer'],
                    '20029': [PermissionDenied, 'Transfer amount exceed your daily limit'],
                    '20030': [BadRequest, 'You have no position on self trading pair'],
                    '20032': [PermissionDenied, 'Withdrawal limited'],
                    '20033': [BadRequest, 'Wrong Withdrawal ID'],
                    '20034': [PermissionDenied, 'Withdrawal service of self crypto has been closed'],
                    '20035': [PermissionDenied, 'Withdrawal limit'],
                    '20036': [ExchangeError, 'Withdrawal cancellation failed'],
                    '20037': [InvalidAddress, 'The withdrawal address, Tag or chain type is not included in the withdrawal management list'],
                    '20038': [InvalidAddress, 'The withdrawal address is not on the white list'],
                    '20039': [ExchangeError, "Can't be canceled in current status"],
                    '20040': [RateLimitExceeded, 'Withdraw too frequently; limitation: 3 times a minute, 100 times a day'],
                    '20041': [PermissionDenied, 'Beyond the daily withdrawal limit'],
                    '20042': [BadSymbol, 'Current trading pair does not support API trading'],
                },
                'broad': {
                },
            },
            'options': {
                'defaultType': 'spot',
                'types': ['spot', 'margin', 'otc'],
            },
            'commonCurrencies': {
                'BHT': 'Black House Test',
                'MBN': 'Mobilian Coin',
                'TEL': 'TEL666',
            },
        })

    async def fetch_currencies(self, params={}):
        response = await self.publicGetCurrencies(params)
        #
        #     {
        #         "data":[
        #             {
        #                 "deposit_status":1,
        #                 "min_deposit_amount":10,
        #                 "withdraw_fee_rate":0,
        #                 "min_withdraw_amount":10,
        #                 "min_withdraw_fee":5,
        #                 "currency":"USDT",
        #                 "withdraw_status":0,
        #                 "chain":"OMNI"
        #             },
        #             {
        #                 "deposit_status":1,
        #                 "min_deposit_amount":10,
        #                 "withdraw_fee_rate":0,
        #                 "min_withdraw_amount":10,
        #                 "min_withdraw_fee":3,
        #                 "currency":"USDT",
        #                 "withdraw_status":1,
        #                 "chain":"ERC20"
        #             },
        #             {
        #                 "deposit_status":0,
        #                 "min_deposit_amount":0,
        #                 "withdraw_fee_rate":0,
        #                 "min_withdraw_amount":0,
        #                 "min_withdraw_fee":0,
        #                 "currency":"DGF13",
        #                 "withdraw_status":0,
        #                 "chain":""
        #             },
        #         ],
        #         "code":200
        #     }
        #
        data = self.safe_value(response, 'data', [])
        result = {}
        for i in range(0, len(data)):
            currency = data[i]
            id = self.safe_string(currency, 'currency')
            code = self.safe_currency_code(id)
            depositStatus = self.safe_value(currency, 'deposit_status', 1)
            withdrawStatus = self.safe_value(currency, 'withdraw_status', 1)
            active = depositStatus and withdrawStatus
            fee = self.safe_float(currency, 'withdraw_fee_rate')
            if code in result:
                if isinstance(result[code]['info'], list):
                    result[code]['info'].append(currency)
                else:
                    result[code]['info'] = [result[code]['info'], currency]
            else:
                result[code] = {
                    'id': id,
                    'code': code,
                    'info': currency,
                    'type': None,
                    'name': None,
                    'active': active,
                    'fee': fee,
                    'precision': 8,  # todo fix hardcoded value
                    'limits': {
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                        'price': {
                            'min': None,
                            'max': None,
                        },
                        'cost': {
                            'min': None,
                            'max': None,
                        },
                        'withdraw': {
                            'min': self.safe_float(currency, 'min_withdraw_amount'),
                            'max': None,
                        },
                    },
                }
        return result

    async def fetch_markets(self, params={}):
        options = self.safe_value(self.options, 'fetchMarkets', {})
        method = self.safe_string(options, 'method', 'fetch_markets_v2')
        return await getattr(self, method)(params)

    async def fetch_markets_v2(self, params={}):
        response = await self.publicGetTradesSymbols(params)
        #
        #     {
        #         "symbol_list":[
        #             {
        #                 "order_types":["LIMIT","MARKET"],
        #                 "quote_asset":"USDT",
        #                 "minimum_value":2,
        #                 "amount_precision":4,
        #                 "status":"TRADING",
        #                 "minimum_amount":0.0001,
        #                 "symbol":"BTC_USDT",
        #                 "is_allow":1,
        #                 "zone":"MAIN",
        #                 "base_asset":"BTC",
        #                 "price_precision":2
        #             }
        #         ],
        #         "code":0
        #     }
        #
        markets = self.safe_value(response, 'symbol_list', [])
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'symbol')
            baseId = self.safe_string(market, 'base_asset')
            quoteId = self.safe_string(market, 'quote_asset')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'amount': self.safe_integer(market, 'amount_precision'),
                'price': self.safe_integer(market, 'price_precision'),
            }
            limits = {
                'amount': {
                    'min': self.safe_float(market, 'minimum_amount'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.safe_float(market, 'minimum_value'),
                    'max': None,
                },
            }
            #
            # The status is documented in the exchange API docs as follows:
            # TRADING, HALT(delisted), BREAK(trading paused)
            # https://docs.digifinex.vip/en-ww/v3/#/public/spot/symbols
            # However, all spot markets actually have status == 'HALT'
            # despite that they appear to be active on the exchange website.
            # Apparently, we can't trust self status.
            # status = self.safe_string(market, 'status')
            # active = (status == 'TRADING')
            #
            isAllowed = self.safe_value(market, 'is_allow', 1)
            active = True if isAllowed else False
            type = 'spot'
            spot = (type == 'spot')
            margin = (type == 'margin')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'type': type,
                'spot': spot,
                'margin': margin,
                'precision': precision,
                'limits': limits,
                'info': market,
            })
        return result

    async def fetch_markets_v1(self, params={}):
        response = await self.publicGetMarkets(params)
        #
        #     {
        #         "data": [
        #             {
        #                 "volume_precision":4,
        #                 "price_precision":2,
        #                 "market":"btc_usdt",
        #                 "min_amount":2,
        #                 "min_volume":0.0001
        #             },
        #         ],
        #         "date":1564507456,
        #         "code":0
        #     }
        #
        markets = self.safe_value(response, 'data', [])
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'market')
            baseId, quoteId = id.split('_')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'amount': self.safe_integer(market, 'volume_precision'),
                'price': self.safe_integer(market, 'price_precision'),
            }
            limits = {
                'amount': {
                    'min': self.safe_float(market, 'min_volume'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.safe_float(market, 'min_amount'),
                    'max': None,
                },
            }
            active = None
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'precision': precision,
                'limits': limits,
                'info': market,
            })
        return result

    async def fetch_balance(self, params={}):
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        method = 'privateGet' + self.capitalize(type) + 'Assets'
        response = await getattr(self, method)(params)
        #
        #     {
        #         "code": 0,
        #         "list": [
        #             {
        #                 "currency": "BTC",
        #                 "free": 4723846.89208129,
        #                 "total": 0
        #             }
        #         ]
        #     }
        balances = self.safe_value(response, 'list', [])
        result = {'info': response}
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['used'] = self.safe_float(balance, 'frozen')
            account['free'] = self.safe_float(balance, 'free')
            account['total'] = self.safe_float(balance, 'total')
            result[code] = account
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 10, max 150
        response = await self.publicGetOrderBook(self.extend(request, params))
        #
        #     {
        #         "bids": [
        #             [9605.77,0.0016],
        #             [9605.46,0.0003],
        #             [9602.04,0.0127],
        #         ],
        #         "asks": [
        #             [9627.22,0.025803],
        #             [9627.12,0.168543],
        #             [9626.52,0.0011529],
        #         ],
        #         "date":1564509499,
        #         "code":0
        #     }
        #
        timestamp = self.safe_timestamp(response, 'date')
        return self.parse_order_book(response, timestamp)

    async def fetch_tickers(self, symbols=None, params={}):
        apiKey = self.safe_value(params, 'apiKey', self.apiKey)
        if not apiKey:
            raise ArgumentsRequired(self.id + ' fetchTickers() is a private v2 endpoint that requires an `exchange.apiKey` credential or an `apiKey` extra parameter')
        await self.load_markets()
        request = {
            'apiKey': apiKey,
        }
        response = await self.v2GetTicker(self.extend(request, params))
        #
        #     {
        #         "ticker":{
        #             "btc_eth":{
        #                 "last":0.021957,
        #                 "base_vol":2249.3521732227,
        #                 "change":-0.6,
        #                 "vol":102443.5111,
        #                 "sell":0.021978,
        #                 "low":0.021791,
        #                 "buy":0.021946,
        #                 "high":0.022266
        #             }
        #         },
        #         "date":1564518452,
        #         "code":0
        #     }
        #
        result = {}
        tickers = self.safe_value(response, 'ticker', {})
        date = self.safe_integer(response, 'date')
        reversedMarketIds = list(tickers.keys())
        for i in range(0, len(reversedMarketIds)):
            reversedMarketId = reversedMarketIds[i]
            ticker = self.extend({
                'date': date,
            }, tickers[reversedMarketId])
            quoteId, baseId = reversedMarketId.split('_')
            marketId = baseId.upper() + '_' + quoteId.upper()
            market = self.safe_market(marketId, None, '_')
            symbol = market['symbol']
            result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array(result, 'symbol', symbols)

    async def fetch_ticker(self, symbol, params={}):
        apiKey = self.safe_value(params, 'apiKey', self.apiKey)
        if not apiKey:
            raise ArgumentsRequired(self.id + ' fetchTicker() is a private v2 endpoint that requires an `exchange.apiKey` credential or an `apiKey` extra parameter')
        await self.load_markets()
        market = self.market(symbol)
        # reversed base/quote in v2
        marketId = market['quoteId'].lower() + '_' + market['baseId'].lower()
        request = {
            'symbol': marketId,
            'apiKey': apiKey,
        }
        response = await self.v2GetTicker(self.extend(request, params))
        #
        #     {
        #         "ticker":{
        #             "btc_eth":{
        #                 "last":0.021957,
        #                 "base_vol":2249.3521732227,
        #                 "change":-0.6,
        #                 "vol":102443.5111,
        #                 "sell":0.021978,
        #                 "low":0.021791,
        #                 "buy":0.021946,
        #                 "high":0.022266
        #             }
        #         },
        #         "date":1564518452,
        #         "code":0
        #     }
        #
        date = self.safe_integer(response, 'date')
        ticker = self.safe_value(response, 'ticker', {})
        result = self.safe_value(ticker, marketId, {})
        result = self.extend({'date': date}, result)
        return self.parse_ticker(result, market)

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker, fetchTickers
        #
        #     {
        #         "last":0.021957,
        #         "base_vol":2249.3521732227,
        #         "change":-0.6,
        #         "vol":102443.5111,
        #         "sell":0.021978,
        #         "low":0.021791,
        #         "buy":0.021946,
        #         "high":0.022266,
        #         "date"1564518452,  # injected from fetchTicker/fetchTickers
        #     }
        #
        symbol = None
        if market is not None:
            symbol = market['symbol']
        timestamp = self.safe_timestamp(ticker, 'date')
        last = self.safe_float(ticker, 'last')
        percentage = self.safe_float(ticker, 'change')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'buy'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'sell'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_float(ticker, 'vol'),
            'quoteVolume': self.safe_float(ticker, 'base_vol'),
            'info': ticker,
        }

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "date":1564520003,
        #         "id":1596149203,
        #         "amount":0.7073,
        #         "type":"buy",
        #         "price":0.02193,
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "symbol": "BTC_USDT",
        #         "order_id": "6707cbdcda0edfaa7f4ab509e4cbf966",
        #         "id": 28457,
        #         "price": 0.1,
        #         "amount": 0,
        #         "fee": 0.096,
        #         "fee_currency": "USDT",
        #         "timestamp": 1499865549,
        #         "side": "buy",
        #         "is_maker": True
        #     }
        #
        id = self.safe_string(trade, 'id')
        orderId = self.safe_string(trade, 'order_id')
        timestamp = self.safe_timestamp_2(trade, 'date', 'timestamp')
        side = self.safe_string_2(trade, 'type', 'side')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        cost = None
        if price is not None:
            if amount is not None:
                cost = price * amount
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market, '_')
        takerOrMaker = self.safe_value(trade, 'is_maker')
        feeCost = self.safe_float(trade, 'fee')
        fee = None
        if feeCost is not None:
            feeCurrencyId = self.safe_string(trade, 'fee_currency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'order': orderId,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'takerOrMaker': takerOrMaker,
            'fee': fee,
        }

    async def fetch_time(self, params={}):
        response = await self.publicGetTime(params)
        #
        #     {
        #         "server_time": 1589873762,
        #         "code": 0
        #     }
        #
        return self.safe_timestamp(response, 'server_time')

    async def fetch_status(self, params={}):
        await self.publicGetPing(params)
        #
        #     {
        #         "msg": "pong",
        #         "code": 0
        #     }
        #
        self.status = self.extend(self.status, {
            'status': 'ok',
            'updated': self.milliseconds(),
        })
        return self.status

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 100, max 500
        response = await self.publicGetTrades(self.extend(request, params))
        #
        #     {
        #         "data":[
        #             {
        #                 "date":1564520003,
        #                 "id":1596149203,
        #                 "amount":0.7073,
        #                 "type":"buy",
        #                 "price":0.02193,
        #             },
        #             {
        #                 "date":1564520002,
        #                 "id":1596149165,
        #                 "amount":0.3232,
        #                 "type":"sell",
        #                 "price":0.021927,
        #             },
        #         ],
        #         "code": 0,
        #         "date": 1564520003,
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_trades(data, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1556712900,
        #         2205.899,
        #         0.029967,
        #         0.02997,
        #         0.029871,
        #         0.029927
        #     ]
        #
        return [
            self.safe_timestamp(ohlcv, 0),
            self.safe_float(ohlcv, 5),  # open
            self.safe_float(ohlcv, 3),  # high
            self.safe_float(ohlcv, 4),  # low
            self.safe_float(ohlcv, 2),  # close
            self.safe_float(ohlcv, 1),  # volume
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'period': self.timeframes[timeframe],
            # 'start_time': 1564520003,  # starting timestamp, 200 candles before end_time by default
            # 'end_time': 1564520003,  # ending timestamp, current timestamp by default
        }
        if since is not None:
            startTime = int(since / 1000)
            request['start_time'] = startTime
            if limit is not None:
                duration = self.parse_timeframe(timeframe)
                request['end_time'] = self.sum(startTime, limit * duration)
        elif limit is not None:
            endTime = self.seconds()
            duration = self.parse_timeframe(timeframe)
            request['startTime'] = self.sum(endTime, -limit * duration)
        response = await self.publicGetKline(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "data":[
        #             [1556712900,2205.899,0.029967,0.02997,0.029871,0.029927],
        #             [1556713800,1912.9174,0.029992,0.030014,0.029955,0.02996],
        #             [1556714700,1556.4795,0.029974,0.030019,0.029969,0.02999],
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        request = {
            'market': orderType,
            'symbol': market['id'],
            'amount': self.amount_to_precision(symbol, amount),
            # 'post_only': 0,  # 0 by default, if set to 1 the order will be canceled if it can be executed immediately, making sure there will be no market taking
        }
        suffix = ''
        if type == 'market':
            suffix = '_market'
        else:
            request['price'] = self.price_to_precision(symbol, price)
        request['type'] = side + suffix
        response = await self.privatePostMarketOrderNew(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "order_id": "198361cecdc65f9c8c9bb2fa68faec40"
        #     }
        #
        result = self.parse_order(response, market)
        return self.extend(result, {
            'symbol': symbol,
            'side': side,
            'type': type,
            'amount': amount,
            'price': price,
        })

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        request = {
            'market': orderType,
            'order_id': id,
        }
        response = await self.privatePostMarketOrderCancel(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "success": [
        #             "198361cecdc65f9c8c9bb2fa68faec40",
        #             "3fb0d98e51c18954f10d439a9cf57de0"
        #         ],
        #         "error": [
        #             "78a7104e3c65cc0c5a212a53e76d0205"
        #         ]
        #     }
        #
        canceledOrders = self.safe_value(response, 'success', [])
        numCanceledOrders = len(canceledOrders)
        if numCanceledOrders != 1:
            raise OrderNotFound(self.id + ' cancelOrder ' + id + ' not found')
        return response

    async def cancel_orders(self, ids, symbol=None, params={}):
        await self.load_markets()
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        request = {
            'market': orderType,
            'order_id': ','.join(ids),
        }
        response = await self.privatePostCancelOrder(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "success": [
        #             "198361cecdc65f9c8c9bb2fa68faec40",
        #             "3fb0d98e51c18954f10d439a9cf57de0"
        #         ],
        #         "error": [
        #             "78a7104e3c65cc0c5a212a53e76d0205"
        #         ]
        #     }
        #
        canceledOrders = self.safe_value(response, 'success', [])
        numCanceledOrders = len(canceledOrders)
        if numCanceledOrders < 1:
            raise OrderNotFound(self.id + ' cancelOrders error')
        return response

    def parse_order_status(self, status):
        statuses = {
            '0': 'open',
            '1': 'open',  # partially filled
            '2': 'closed',
            '3': 'canceled',
            '4': 'canceled',  # partially filled and canceled
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "code": 0,
        #         "order_id": "198361cecdc65f9c8c9bb2fa68faec40"
        #     }
        #
        # fetchOrder, fetchOpenOrders, fetchOrders
        #
        #     {
        #         "symbol": "BTC_USDT",
        #         "order_id": "dd3164b333a4afa9d5730bb87f6db8b3",
        #         "created_date": 1562303547,
        #         "finished_date": 0,
        #         "price": 0.1,
        #         "amount": 1,
        #         "cash_amount": 1,
        #         "executed_amount": 0,
        #         "avg_price": 0,
        #         "status": 1,
        #         "type": "buy",
        #         "kind": "margin"
        #     }
        #
        id = self.safe_string(order, 'order_id')
        timestamp = self.safe_timestamp(order, 'created_date')
        lastTradeTimestamp = self.safe_timestamp(order, 'finished_date')
        side = self.safe_string(order, 'type')
        type = None
        if side is not None:
            parts = side.split('_')
            numParts = len(parts)
            if numParts > 1:
                side = parts[0]
                type = parts[1]
            else:
                type = 'limit'
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market, '_')
        amount = self.safe_float(order, 'amount')
        filled = self.safe_float(order, 'executed_amount')
        price = self.safe_float(order, 'price')
        average = self.safe_float(order, 'avg_price')
        remaining = None
        cost = None
        if filled is not None:
            if average is not None:
                cost = filled * average
            if amount is not None:
                remaining = max(0, amount - filled)
        return {
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'cost': cost,
            'average': average,
            'status': status,
            'fee': None,
            'trades': None,
        }

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        await self.load_markets()
        market = None
        request = {
            'market': orderType,
        }
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        response = await self.privateGetMarketOrderCurrent(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "data": [
        #             {
        #                 "symbol": "BTC_USDT",
        #                 "order_id": "dd3164b333a4afa9d5730bb87f6db8b3",
        #                 "created_date": 1562303547,
        #                 "finished_date": 0,
        #                 "price": 0.1,
        #                 "amount": 1,
        #                 "cash_amount": 1,
        #                 "executed_amount": 0,
        #                 "avg_price": 0,
        #                 "status": 1,
        #                 "type": "buy",
        #                 "kind": "margin"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        await self.load_markets()
        market = None
        request = {
            'market': orderType,
        }
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['start_time'] = int(since / 1000)  # default 3 days from now, max 30 days
        if limit is not None:
            request['limit'] = limit  # default 10, max 100
        response = await self.privateGetMarketOrderHistory(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "data": [
        #             {
        #                 "symbol": "BTC_USDT",
        #                 "order_id": "dd3164b333a4afa9d5730bb87f6db8b3",
        #                 "created_date": 1562303547,
        #                 "finished_date": 0,
        #                 "price": 0.1,
        #                 "amount": 1,
        #                 "cash_amount": 1,
        #                 "executed_amount": 0,
        #                 "avg_price": 0,
        #                 "status": 1,
        #                 "type": "buy",
        #                 "kind": "margin"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

    async def fetch_order(self, id, symbol=None, params={}):
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        await self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'market': orderType,
            'order_id': id,
        }
        response = await self.privateGetMarketOrder(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "data": [
        #             {
        #                 "symbol": "BTC_USDT",
        #                 "order_id": "dd3164b333a4afa9d5730bb87f6db8b3",
        #                 "created_date": 1562303547,
        #                 "finished_date": 0,
        #                 "price": 0.1,
        #                 "amount": 1,
        #                 "cash_amount": 1,
        #                 "executed_amount": 0,
        #                 "avg_price": 0,
        #                 "status": 1,
        #                 "type": "buy",
        #                 "kind": "margin"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        order = self.safe_value(data, 0)
        if order is None:
            raise OrderNotFound(self.id + ' fetchOrder() order ' + id + ' not found')
        return self.parse_order(order, market)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        await self.load_markets()
        market = None
        request = {
            'market': orderType,
        }
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['start_time'] = int(since / 1000)  # default 3 days from now, max 30 days
        if limit is not None:
            request['limit'] = limit  # default 10, max 100
        response = await self.privateGetMarketMytrades(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "list": [
        #             {
        #                 "symbol": "BTC_USDT",
        #                 "order_id": "6707cbdcda0edfaa7f4ab509e4cbf966",
        #                 "id": 28457,
        #                 "price": 0.1,
        #                 "amount": 0,
        #                 "fee": 0.096,
        #                 "fee_currency": "USDT",
        #                 "timestamp": 1499865549,
        #                 "side": "buy",
        #                 "is_maker": True
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'list', [])
        return self.parse_trades(data, market, since, limit)

    def parse_ledger_entry_type(self, type):
        types = {}
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        #     {
        #         "currency_mark": "BTC",
        #         "type": 100234,
        #         "num": 28457,
        #         "balance": 0.1,
        #         "time": 1546272000
        #     }
        #
        id = self.safe_string(item, 'num')
        account = None
        type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
        code = self.safe_currency_code(self.safe_string(item, 'currency_mark'), currency)
        timestamp = self.safe_timestamp(item, 'time')
        before = None
        after = self.safe_float(item, 'balance')
        status = 'ok'
        return {
            'info': item,
            'id': id,
            'direction': None,
            'account': account,
            'referenceId': None,
            'referenceAccount': None,
            'type': type,
            'currency': code,
            'amount': None,
            'before': before,
            'after': after,
            'status': status,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': None,
        }

    async def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        defaultType = self.safe_string(self.options, 'defaultType', 'spot')
        orderType = self.safe_string(params, 'type', defaultType)
        params = self.omit(params, 'type')
        await self.load_markets()
        request = {
            'market': orderType,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency_mark'] = currency['id']
        if since is not None:
            request['start_time'] = int(since / 1000)
        if limit is not None:
            request['limit'] = limit  # default 100, max 1000
        response = await self.privateGetMarketFinancelog(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "data": {
        #             "total": 521,
        #             "finance": [
        #                 {
        #                     "currency_mark": "BTC",
        #                     "type": 100234,
        #                     "num": 28457,
        #                     "balance": 0.1,
        #                     "time": 1546272000
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        items = self.safe_value(data, 'finance', [])
        return self.parse_ledger(items, currency, since, limit)

    def parse_deposit_addresses(self, addresses):
        result = {}
        for i in range(0, len(addresses)):
            address = self.parse_deposit_address(addresses[i])
            code = address['currency']
            result[code] = address
        return result

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     {
        #         "addressTag":"",
        #         "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373",
        #         "currency":"USDT",
        #         "chain":"ERC20"
        #     }
        #
        address = self.safe_string(depositAddress, 'address')
        tag = self.safe_string(depositAddress, 'addressTag')
        currencyId = self.safe_string_upper(depositAddress, 'currency')
        code = self.safe_currency_code(currencyId)
        return {
            'info': depositAddress,
            'code': code,
            'address': address,
            'tag': tag,
        }

    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))
        #
        #     {
        #         "data":[
        #             {
        #                 "addressTag":"",
        #                 "address":"0xf1104d9f8624f89775a3e9d480fc0e75a8ef4373",
        #                 "currency":"USDT",
        #                 "chain":"ERC20"
        #             }
        #         ],
        #         "code":200
        #     }
        #
        data = self.safe_value(response, 'data', [])
        addresses = self.parse_deposit_addresses(data)
        address = self.safe_value(addresses, code)
        if address is None:
            raise InvalidAddress(self.id + ' fetchDepositAddress did not return an address for ' + code + ' - create the deposit address in the user settings on the exchange website first.')
        return address

    async def fetch_transactions_by_type(self, type, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        currency = None
        request = {
            # 'currency': currency['id'],
            # 'from': 'fromId',  # When direct is' prev ', from is 1, returning from old to new ascending, when direct is' next ', from is the ID of the most recent record, returned from the old descending order
            # 'size': 100,  # default 100, max 500
            # 'direct': 'prev',  # "prev" ascending, "next" descending
        }
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if limit is not None:
            request['size'] = min(500, limit)
        method = 'privateGetDepositHistory' if (type == 'deposit') else 'privateGetWithdrawHistory'
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "code": 200,
        #         "data": [
        #             {
        #                 "id": 1171,
        #                 "currency": "xrp",
        #                 "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b",
        #                 "chain": "",
        #                 "amount": 7.457467,
        #                 "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm",
        #                 "memo": "100040",
        #                 "fee": 0,
        #                 "state": "safe",
        #                 "created_date": "2020-04-20 11:23:00",
        #                 "finished_date": "2020-04-20 13:23:00"
        #             },
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit, {'type': type})

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

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        return await self.fetch_transactions_by_type('withdrawal', code, since, limit, params)

    def parse_transaction_status(self, status):
        statuses = {
            '0': 'pending',  # Email Sent
            '1': 'canceled',  # Cancelled(different from 1 = ok in deposits)
            '2': 'pending',  # Awaiting Approval
            '3': 'failed',  # Rejected
            '4': 'pending',  # Processing
            '5': 'failed',  # Failure
            '6': 'ok',  # Completed
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # withdraw
        #
        #     {
        #         "code": 200,
        #         "withdraw_id": 700
        #     }
        #
        # fetchDeposits, fetchWithdrawals
        #
        #     {
        #         "id": 1171,
        #         "currency": "xrp",
        #         "hash": "ed03094b84eafbe4bc16e7ef766ee959885ee5bcb265872baaa9c64e1cf86c2b",
        #         "chain": "",
        #         "amount": 7.457467,
        #         "address": "rae93V8d2mdoUQHwBDBdM4NHCMehRJAsbm",
        #         "memo": "100040",
        #         "fee": 0,
        #         "state": "safe",
        #         "created_date": "2020-04-20 11:23:00",
        #         "finished_date": "2020-04-20 13:23:00"
        #     }
        #
        id = self.safe_string_2(transaction, 'id', 'withdraw_id')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'memo')  # set but unused
        if tag is not None:
            if len(tag) < 1:
                tag = None
        txid = self.safe_string(transaction, 'hash')
        currencyId = self.safe_string_upper(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        timestamp = self.parse8601(self.safe_string(transaction, 'created_date'))
        updated = self.parse8601(self.safe_string(transaction, 'finished_date'))
        status = self.parse_transaction_status(self.safe_string(transaction, 'state'))
        amount = self.safe_float(transaction, 'amount')
        feeCost = self.safe_float(transaction, 'fee')
        fee = None
        if feeCost is not None:
            fee = {'currency': code, 'cost': feeCost}
        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': None,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        request = {
            # 'chain': 'ERC20', 'OMNI', 'TRC20',  # required for USDT
            'address': address,
            'amount': float(amount),
            'currency': currency['id'],
        }
        if tag is not None:
            request['memo'] = tag
        response = await self.privatePostWithdrawNew(self.extend(request, params))
        #
        #     {
        #         "code": 200,
        #         "withdraw_id": 700
        #     }
        #
        return self.parse_transaction(response, currency)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        version = api if (api == 'v2') else self.version
        url = self.urls['api'] + '/' + version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        urlencoded = self.urlencode(self.keysort(query))
        if api == 'private':
            nonce = str(self.nonce())
            auth = urlencoded
            # the signature is not time-limited :\
            signature = self.hmac(self.encode(auth), self.encode(self.secret))
            if method == 'GET':
                if urlencoded:
                    url += '?' + urlencoded
            elif method == 'POST':
                headers = {
                    'Content-Type': 'application/x-www-form-urlencoded',
                }
                if urlencoded:
                    body = urlencoded
            headers = {
                'ACCESS-KEY': self.apiKey,
                'ACCESS-SIGN': signature,
                'ACCESS-TIMESTAMP': nonce,
            }
        else:
            if urlencoded:
                url += '?' + urlencoded
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody):
        if not response:
            return  # fall back to default error handler
        code = self.safe_string(response, 'code')
        if (code == '0') or (code == '200'):
            return  # no error
        feedback = self.id + ' ' + responseBody
        if code is None:
            raise BadResponse(feedback)
        unknownError = [ExchangeError, feedback]
        ExceptionClass, message = self.safe_value(self.exceptions['exact'], code, unknownError)
        raise ExceptionClass(message)
