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

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

from ccxt.base.exchange import Exchange
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.decimal_to_precision import TICK_SIZE


class delta(Exchange):

    def describe(self):
        return self.deep_extend(super(delta, self).describe(), {
            'id': 'delta',
            'name': 'Delta Exchange',
            'countries': ['VC'],  # Saint Vincent and the Grenadines
            'rateLimit': 300,
            'version': 'v2',
            # new metainfo interface
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchDepositAddress': True,
                'fetchCurrencies': True,
                'fetchLedger': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrderBook': True,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '2h': '2h',
                '4h': '4h',
                '6h': '6h',
                '1d': '1d',
                '7d': '7d',
                '1w': '1w',
                '2w': '2w',
                '1M': '30d',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/99450025-3be60a00-2931-11eb-9302-f4fd8d8589aa.jpg',
                'test': {
                    'public': 'https://testnet-api.delta.exchange',
                    'private': 'https://testnet-api.delta.exchange',
                },
                'api': {
                    'public': 'https://api.delta.exchange',
                    'private': 'https://api.delta.exchange',
                },
                'www': 'https://www.delta.exchange',
                'doc': [
                    'https://docs.delta.exchange',
                ],
                'fees': 'https://www.delta.exchange/fees',
                'referral': 'https://www.delta.exchange/app/signup/?code=IULYNB',
            },
            'api': {
                'public': {
                    'get': [
                        'assets',
                        'settings',
                        'indices',
                        'products',
                        'tickers',
                        'tickers/{symbol}',
                        'l2orderbook/{symbol}',
                        'trades/{symbol}',
                        'history/candles',
                        'history/sparklines',
                    ],
                },
                'private': {
                    'get': [
                        'orders',
                        'orders/leverage',
                        'positions',
                        'positions/margined',
                        'orders/history',
                        'fills',
                        'fills/history/download/csv',
                        'wallet/balances',
                        'wallet/transactions',
                        'wallet/transactions/download',
                        'deposits/address',
                    ],
                    'post': [
                        'orders',
                        'orders/batch',
                        'orders/leverage',
                        'positions/change_margin',
                    ],
                    'put': [
                        'orders',
                        'orders/batch',
                    ],
                    'delete': [
                        'orders',
                        'orders/all',
                        'orders/batch',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': 0.15 / 100,
                    'maker': 0.10 / 100,
                    'tiers': {
                        'taker': [
                            [0, 0.15 / 100],
                            [100, 0.13 / 100],
                            [250, 0.13 / 100],
                            [1000, 0.1 / 100],
                            [5000, 0.09 / 100],
                            [10000, 0.075 / 100],
                            [20000, 0.065 / 100],
                        ],
                        'maker': [
                            [0, 0.1 / 100],
                            [100, 0.1 / 100],
                            [250, 0.09 / 100],
                            [1000, 0.075 / 100],
                            [5000, 0.06 / 100],
                            [10000, 0.05 / 100],
                            [20000, 0.05 / 100],
                        ],
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'requiredCredentials': {
                'apiKey': True,
                'secret': False,
            },
            'exceptions': {
                'exact': {
                    # Margin required to place order with selected leverage and quantity is insufficient.
                    'insufficient_margin': InsufficientFunds,  # {"error":{"code":"insufficient_margin","context":{"available_balance":"0.000000000000000000","required_additional_balance":"1.618626000000000000000000000"}},"success":false}
                    'order_size_exceed_available': InvalidOrder,  # The order book doesn't have sufficient liquidity, hence the order couldnt be filled, for example, ioc orders
                    'risk_limits_breached': BadRequest,  # orders couldn't be placed as it will breach allowed risk limits.
                    'invalid_contract': BadSymbol,  # The contract/product is either doesn't exist or has already expired.
                    'immediate_liquidation': InvalidOrder,  # Order will cause immediate liquidation.
                    'out_of_bankruptcy': InvalidOrder,  # Order prices are out of position bankruptcy limits.
                    'self_matching_disrupted_post_only': InvalidOrder,  # Self matching is not allowed during auction.
                    'immediate_execution_post_only': InvalidOrder,  # orders couldn't be placed as it includes post only orders which will be immediately executed
                    'bad_schema': BadRequest,  # {"error":{"code":"bad_schema","context":{"schema_errors":[{"code":"validation_error","message":"id is required","param":""}]}},"success":false}
                    'invalid_api_key': AuthenticationError,  # {"success":false,"error":{"code":"invalid_api_key"}}
                    'invalid_signature': AuthenticationError,  # {"success":false,"error":{"code":"invalid_signature"}}
                    'open_order_not_found': OrderNotFound,  # {"error":{"code":"open_order_not_found"},"success":false}
                    'unavailable': ExchangeNotAvailable,  # {"error":{"code":"unavailable"},"success":false}
                },
                'broad': {
                },
            },
        })

    def fetch_time(self, params={}):
        response = self.publicGetSettings(params)
        #
        #     {
        #         "result":{
        #             "server_time":1605472733766141,
        #             "deto_referral_mining_daily_reward":"25000",
        #             "deto_total_reward_pool":"100000000",
        #             "deto_trade_mining_daily_reward":"75000",
        #             "kyc_deposit_limit":"20",
        #             "kyc_withdrawal_limit":"2",
        #             "under_maintenance":"false"
        #         },
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.safe_integer_product(result, 'server_time', 0.001)

    def fetch_status(self, params={}):
        response = self.publicGetSettings(params)
        result = self.safe_value(response, 'result', {})
        underMaintenance = self.safe_value(result, 'under_maintenance')
        status = 'maintenance' if (underMaintenance == 'true') else 'ok'
        updated = self.safe_integer_product(result, 'server_time', 0.001)
        self.status = self.extend(self.status, {
            'status': status,
            'updated': updated,
        })
        return self.status

    def fetch_currencies(self, params={}):
        response = self.publicGetAssets(params)
        #
        #     {
        #         "result":[
        #             {
        #                 "base_withdrawal_fee":"0.0005",
        #                 "deposit_status":"enabled",
        #                 "id":2,
        #                 "interest_credit":true,
        #                 "interest_slabs":[
        #                     {"limit":"0.1","rate":"0"},
        #                     {"limit":"1","rate":"0.05"},
        #                     {"limit":"5","rate":"0.075"},
        #                     {"limit":"10","rate":"0.1"},
        #                     {"limit":"9999999999999999","rate":"0"}
        #                 ],
        #                 "kyc_deposit_limit":"10",
        #                 "kyc_withdrawal_limit":"2",
        #                 "min_withdrawal_amount":"0.001",
        #                 "minimum_precision":4,
        #                 "name":"Bitcoin",
        #                 "precision":8,
        #                 "sort_priority":1,
        #                 "symbol":"BTC",
        #                 "variable_withdrawal_fee":"0",
        #                 "withdrawal_status":"enabled"
        #             },
        #         ],
        #         "success":true
        #     }
        #
        currencies = self.safe_value(response, 'result', [])
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'symbol')
            numericId = self.safe_integer(currency, 'id')
            code = self.safe_currency_code(id)
            depositStatus = self.safe_string(currency, 'deposit_status')
            withdrawalStatus = self.safe_string(currency, 'withdrawal_status')
            depositsEnabled = (depositStatus == 'enabled')
            withdrawalsEnabled = (withdrawalStatus == 'enabled')
            active = depositsEnabled and withdrawalsEnabled
            precision = self.safe_integer(currency, 'precision')
            result[code] = {
                'id': id,
                'numericId': numericId,
                'code': code,
                'name': self.safe_string(currency, 'name'),
                'info': currency,  # the original payload
                'active': active,
                'fee': self.safe_float(currency, 'base_withdrawal_fee'),
                'precision': 1 / math.pow(10, precision),
                'limits': {
                    'amount': {'min': None, 'max': None},
                    'price': {'min': None, 'max': None},
                    'cost': {'min': None, 'max': None},
                    'withdraw': {
                        'min': self.safe_float(currency, 'min_withdrawal_amount'),
                        'max': None,
                    },
                },
            }
        return result

    def load_markets(self, reload=False, params={}):
        markets = super(delta, self).load_markets(reload, params)
        currenciesByNumericId = self.safe_value(self.options, 'currenciesByNumericId')
        if (currenciesByNumericId is None) or reload:
            self.options['currenciesByNumericId'] = self.index_by(self.currencies, 'numericId')
        marketsByNumericId = self.safe_value(self.options, 'marketsByNumericId')
        if (marketsByNumericId is None) or reload:
            self.options['marketsByNumericId'] = self.index_by(self.markets, 'numericId')
        return markets

    def fetch_markets(self, params={}):
        response = self.publicGetProducts(params)
        #
        #     {
        #         "meta":{
        #             "after":null,
        #             "before":null,
        #             "limit":100,
        #             "total_count":81
        #         },
        #         "result":[
        #             {
        #                 "annualized_funding":"5.475000000000000000",
        #                 "is_quanto":false,
        #                 "ui_config":{
        #                     "default_trading_view_candle":"15",
        #                     "leverage_slider_values":[1,3,5,10,25,50],
        #                     "price_clubbing_values":[0.001,0.005,0.05,0.1,0.5,1,5],
        #                     "show_bracket_orders":false,
        #                     "sort_priority":29,
        #                     "tags":[]
        #                 },
        #                 "basis_factor_max_limit":"0.15",
        #                 "symbol":"P-LINK-D-151120",
        #                 "id":1584,
        #                 "default_leverage":"5.000000000000000000",
        #                 "maker_commission_rate":"0.0005",
        #                 "contract_unit_currency":"LINK",
        #                 "strike_price":"12.507948",
        #                 "settling_asset":{
        #                     # asset structure
        #                 },
        #                 "auction_start_time":null,
        #                 "auction_finish_time":null,
        #                 "settlement_time":"2020-11-15T12:00:00Z",
        #                 "launch_time":"2020-11-14T11:55:05Z",
        #                 "spot_index":{
        #                     # index structure
        #                 },
        #                 "trading_status":"operational",
        #                 "tick_size":"0.001",
        #                 "position_size_limit":100000,
        #                 "notional_type":"vanilla",  # vanilla, inverse
        #                 "price_band":"0.4",
        #                 "barrier_price":null,
        #                 "description":"Daily LINK PUT options quoted in USDT and settled in USDT",
        #                 "insurance_fund_margin_contribution":"1",
        #                 "quoting_asset":{
        #                     # asset structure
        #                 },
        #                 "liquidation_penalty_factor":"0.2",
        #                 "product_specs":{"max_volatility":3,"min_volatility":0.3,"spot_price_band":"0.40"},
        #                 "initial_margin_scaling_factor":"0.0001",
        #                 "underlying_asset":{
        #                     # asset structure
        #                 },
        #                 "state":"live",
        #                 "contract_value":"1",
        #                 "initial_margin":"2",
        #                 "impact_size":5000,
        #                 "settlement_price":null,
        #                 "contract_type":"put_options",  # put_options, call_options, move_options, perpetual_futures, interest_rate_swaps, futures, spreads
        #                 "taker_commission_rate":"0.0005",
        #                 "maintenance_margin":"1",
        #                 "short_description":"LINK Daily PUT Options",
        #                 "maintenance_margin_scaling_factor":"0.00005",
        #                 "funding_method":"mark_price",
        #                 "max_leverage_notional":"20000"
        #             },
        #         ],
        #         "success":true
        #     }
        #
        markets = self.safe_value(response, 'result', [])
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            type = self.safe_string(market, 'contract_type')
            # settlingAsset = self.safe_value(market, 'settling_asset', {})
            quotingAsset = self.safe_value(market, 'quoting_asset', {})
            underlyingAsset = self.safe_value(market, 'underlying_asset', {})
            baseId = self.safe_string(underlyingAsset, 'symbol')
            quoteId = self.safe_string(quotingAsset, 'symbol')
            id = self.safe_string(market, 'symbol')
            numericId = self.safe_integer(market, 'id')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = id
            swap = False
            future = False
            option = False
            if type == 'perpetual_futures':
                type = 'swap'
                swap = True
                future = False
                option = False
                symbol = base + '/' + quote
            elif (type == 'call_options') or (type == 'put_options') or (type == 'move_options'):
                type = 'option'
                swap = False
                option = True
                future = False
            elif type == 'futures':
                type = 'future'
                swap = False
                option = False
                future = True
            precision = {
                'amount': 1.0,  # number of contracts
                'price': self.safe_float(market, 'tick_size'),
            }
            limits = {
                'amount': {
                    'min': 1.0,
                    'max': self.safe_float(market, 'position_size_limit'),
                },
                'price': {
                    'min': precision['price'],
                    'max': None,
                },
                'cost': {
                    'min': self.safe_float(market, 'min_size'),
                    'max': None,
                },
            }
            state = self.safe_string(market, 'state')
            active = (state == 'live')
            maker = self.safe_float(market, 'maker_commission_rate')
            taker = self.safe_float(market, 'taker_commission_rate')
            result.append({
                'id': id,
                'numericId': numericId,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'type': type,
                'option': option,
                'swap': swap,
                'future': future,
                'maker': maker,
                'taker': taker,
                'precision': precision,
                'limits': limits,
                'info': market,
                'active': active,
            })
        return result

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker, fetchTickers
        #
        #     {
        #         "close":15837.5,
        #         "high":16354,
        #         "low":15751.5,
        #         "mark_price":"15820.100867",
        #         "open":16140.5,
        #         "product_id":139,
        #         "size":640552,
        #         "spot_price":"15827.050000000001",
        #         "symbol":"BTCUSDT",
        #         "timestamp":1605373550208262,
        #         "turnover":10298630.3735,
        #         "turnover_symbol":"USDT",
        #         "turnover_usd":10298630.3735,
        #         "volume":640.5520000000001
        #     }
        #
        timestamp = self.safe_integer_product(ticker, 'timestamp', 0.001)
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_float(ticker, 'close')
        open = self.safe_float(ticker, 'open')
        change = None
        average = None
        percentage = None
        if (open is not None) and (last is not None):
            change = last - open
            average = self.sum(last, open) / 2
            if open != 0.0:
                percentage = (change / open) * 100
        baseVolume = self.safe_float(ticker, 'volume')
        quoteVolume = self.safe_float(ticker, 'turnover')
        vwap = self.vwap(baseVolume, quoteVolume)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': vwap,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': average,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.publicGetTickersSymbol(self.extend(request, params))
        #
        #     {
        #         "result":{
        #             "close":15837.5,
        #             "high":16354,
        #             "low":15751.5,
        #             "mark_price":"15820.100867",
        #             "open":16140.5,
        #             "product_id":139,
        #             "size":640552,
        #             "spot_price":"15827.050000000001",
        #             "symbol":"BTCUSDT",
        #             "timestamp":1605373550208262,
        #             "turnover":10298630.3735,
        #             "turnover_symbol":"USDT",
        #             "turnover_usd":10298630.3735,
        #             "volume":640.5520000000001
        #         },
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_ticker(result, market)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = self.publicGetTickers(params)
        #
        #     {
        #         "result":[
        #             {
        #                 "close":0.003966,
        #                 "high":0.004032,
        #                 "low":0.003606,
        #                 "mark_price":"0.00396328",
        #                 "open":0.003996,
        #                 "product_id":1327,
        #                 "size":6242,
        #                 "spot_price":"0.0039555",
        #                 "symbol":"AAVEBTC",
        #                 "timestamp":1605374143864107,
        #                 "turnover":23.997904999999996,
        #                 "turnover_symbol":"BTC",
        #                 "turnover_usd":387957.4544782897,
        #                 "volume":6242
        #             },
        #         ],
        #         "success":true
        #     }
        #
        tickers = self.safe_value(response, 'result', [])
        result = {}
        for i in range(0, len(tickers)):
            ticker = self.parse_ticker(tickers[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        request = {
            'symbol': self.market_id(symbol),
        }
        if limit is not None:
            request['depth'] = limit
        response = self.publicGetL2orderbookSymbol(self.extend(request, params))
        #
        #     {
        #         "result":{
        #             "buy":[
        #                 {"price":"15814.0","size":912},
        #                 {"price":"15813.5","size":1279},
        #                 {"price":"15813.0","size":1634},
        #             ],
        #             "sell":[
        #                 {"price":"15814.5","size":625},
        #                 {"price":"15815.0","size":982},
        #                 {"price":"15815.5","size":1328},
        #             ],
        #             "symbol":"BTCUSDT"
        #         },
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_order_book(result, None, 'buy', 'sell', 'price', 'size')

    def parse_trade(self, trade, market=None):
        #
        # public fetchTrades
        #
        #     {
        #         "buyer_role":"maker",
        #         "price":"15896.5",
        #         "seller_role":"taker",
        #         "size":241,
        #         "symbol":"BTCUSDT",
        #         "timestamp":1605376684714595
        #     }
        #
        # private fetchMyTrades
        #
        #     {
        #         "commission":"0.008335000000000000",
        #         "created_at":"2020-11-16T19:07:19Z",
        #         "fill_type":"normal",
        #         "id":"e7ff05c233a74245b72381f8dd91d1ce",
        #         "meta_data":{
        #             "effective_commission_rate":"0.0005",
        #             "order_price":"16249",
        #             "order_size":1,
        #             "order_type":"market_order",
        #             "order_unfilled_size":0,
        #             "trading_fee_credits_used":"0"
        #         },
        #         "order_id":"152999629",
        #         "price":"16669",
        #         "product":{
        #             "contract_type":"perpetual_futures",
        #             "contract_unit_currency":"BTC",
        #             "contract_value":"0.001",
        #             "id":139,
        #             "notional_type":"vanilla",
        #             "quoting_asset":{"minimum_precision":2,"precision":6,"symbol":"USDT"},
        #             "settling_asset":{"minimum_precision":2,"precision":6,"symbol":"USDT"},
        #             "symbol":"BTCUSDT",
        #             "tick_size":"0.5",
        #             "underlying_asset":{"minimum_precision":4,"precision":8,"symbol":"BTC"}
        #         },
        #         "product_id":139,
        #         "role":"taker",
        #         "side":"sell",
        #         "size":1
        #     }
        #
        id = self.safe_string(trade, 'id')
        orderId = self.safe_string(trade, 'order_id')
        timestamp = self.parse8601(self.safe_string(trade, 'created_at'))
        timestamp = self.safe_integer_product(trade, 'timestamp', 0.001, timestamp)
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'size')
        cost = None
        if (amount is not None) and (price is not None):
            cost = amount * price
        product = self.safe_value(trade, 'product', {})
        marketId = self.safe_string(product, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        sellerRole = self.safe_string(trade, 'seller_role')
        side = self.safe_string(trade, 'side')
        if side is None:
            if sellerRole == 'taker':
                side = 'sell'
            elif sellerRole == 'maker':
                side = 'buy'
        takerOrMaker = self.safe_string(trade, 'role')
        metaData = self.safe_value(trade, 'meta_data', {})
        type = self.safe_string(metaData, 'order_type')
        if type is not None:
            type = type.replace('_order', '')
        feeCost = self.safe_float(trade, 'commission')
        fee = None
        if feeCost is not None:
            settlingAsset = self.safe_value(product, 'settling_asset', {})
            feeCurrencyId = self.safe_string(settlingAsset, 'symbol')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        return {
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'takerOrMaker': takerOrMaker,
            'fee': fee,
            'info': trade,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.publicGetTradesSymbol(self.extend(request, params))
        #
        #     {
        #         "result":[
        #             {
        #                 "buyer_role":"maker",
        #                 "price":"15896.5",
        #                 "seller_role":"taker",
        #                 "size":241,
        #                 "symbol":"BTCUSDT",
        #                 "timestamp":1605376684714595
        #             }
        #         ],
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_trades(result, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "time":1605393120,
        #         "open":15989,
        #         "high":15989,
        #         "low":15987.5,
        #         "close":15987.5,
        #         "volume":565
        #     }
        #
        return [
            self.safe_timestamp(ohlcv, 'time'),
            self.safe_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'volume'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'resolution': self.timeframes[timeframe],
        }
        duration = self.parse_timeframe(timeframe)
        limit = limit if limit else 2000  # max 2000
        if since is None:
            end = self.seconds()
            request['end'] = end
            request['start'] = end - limit * duration
        else:
            start = int(since / 1000)
            request['start'] = start
            request['end'] = self.sum(start, limit * duration)
        response = self.publicGetHistoryCandles(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "result":[
        #             {"time":1605393120,"open":15989,"high":15989,"low":15987.5,"close":15987.5,"volume":565},
        #             {"time":1605393180,"open":15966,"high":15966,"low":15959,"close":15959,"volume":24},
        #             {"time":1605393300,"open":15973,"high":15973,"low":15973,"close":15973,"volume":1288},
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_ohlcvs(result, market, timeframe, since, limit)

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privateGetWalletBalances(params)
        #
        #     {
        #         "result":[
        #             {
        #                 "asset_id":1,
        #                 "available_balance":"0",
        #                 "balance":"0",
        #                 "commission":"0",
        #                 "id":154883,
        #                 "interest_credit":"0",
        #                 "order_margin":"0",
        #                 "pending_referral_bonus":"0",
        #                 "pending_trading_fee_credit":"0",
        #                 "position_margin":"0",
        #                 "trading_fee_credit":"0",
        #                 "user_id":22142
        #             },
        #         ],
        #         "success":true
        #     }
        #
        balances = self.safe_value(response, 'result', [])
        result = {'info': response}
        currenciesByNumericId = self.safe_value(self.options, 'currenciesByNumericId', {})
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'asset_id')
            currency = self.safe_value(currenciesByNumericId, currencyId)
            code = currencyId if (currency is None) else currency['code']
            account = self.account()
            account['total'] = self.safe_float(balance, 'balance')
            account['free'] = self.safe_float(balance, 'available_balance')
            result[code] = account
        return self.parse_balance(result)

    def fetch_position(self, symbol, params=None):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'product_id': market['numericId'],
        }
        response = self.privateGetPositions(self.extend(request, params))
        #
        #     {
        #         "result":{
        #             "entry_price":null,
        #             "size":0,
        #             "timestamp":1605454074268079
        #         },
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return result

    def fetch_positions(self, symbols=None, since=None, limit=None, params={}):
        self.load_markets()
        response = self.privateGetPositionsMargined(params)
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "user_id": 0,
        #                 "size": 0,
        #                 "entry_price": "string",
        #                 "margin": "string",
        #                 "liquidation_price": "string",
        #                 "bankruptcy_price": "string",
        #                 "adl_level": 0,
        #                 "product_id": 0
        #             }
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return result

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

    def parse_order(self, order, market=None):
        #
        # createOrder, cancelOrder, editOrder, fetchOpenOrders, fetchClosedOrders
        #
        #     {
        #         "average_fill_price":null,
        #         "bracket_order":null,
        #         "bracket_stop_loss_limit_price":null,
        #         "bracket_stop_loss_price":null,
        #         "bracket_take_profit_limit_price":null,
        #         "bracket_take_profit_price":null,
        #         "bracket_trail_amount":null,
        #         "cancellation_reason":null,
        #         "client_order_id":null,
        #         "close_on_trigger":"false",
        #         "commission":"0",
        #         "created_at":"2020-11-16T02:38:26Z",
        #         "id":152870626,
        #         "limit_price":"10000",
        #         "meta_data":{"source":"api"},
        #         "order_type":"limit_order",
        #         "paid_commission":"0",
        #         "product_id":139,
        #         "reduce_only":false,
        #         "side":"buy",
        #         "size":0,
        #         "state":"open",
        #         "stop_order_type":null,
        #         "stop_price":null,
        #         "stop_trigger_method":"mark_price",
        #         "time_in_force":"gtc",
        #         "trail_amount":null,
        #         "unfilled_size":0,
        #         "user_id":22142
        #     }
        #
        id = self.safe_string(order, 'id')
        clientOrderId = self.safe_string(order, 'client_order_id')
        timestamp = self.parse8601(self.safe_string(order, 'created_at'))
        marketId = self.safe_string(order, 'product_id')
        marketsByNumericId = self.safe_value(self.options, 'marketsByNumericId', {})
        market = self.safe_value(marketsByNumericId, marketId, market)
        symbol = marketId if (market is None) else market['symbol']
        status = self.parse_order_status(self.safe_string(order, 'state'))
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'order_type')
        type = type.replace('_order', '')
        price = self.safe_float(order, 'limit_price')
        amount = self.safe_float(order, 'size')
        remaining = self.safe_float(order, 'unfilled_size')
        filled = None
        if (amount is not None) and (remaining is not None):
            filled = max(0, amount - remaining)
        cost = None
        average = self.safe_float(order, 'average_fill_price')
        if (average is not None) and filled:
            cost = average * filled
        fee = None
        feeCost = self.safe_float(order, 'paid_commission')
        if feeCost is not None:
            feeCurrencyCode = None
            if market is not None:
                settlingAsset = self.safe_value(market['info'], 'settling_asset', {})
                feeCurrencyId = self.safe_string(settlingAsset, 'symbol')
                feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        return {
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': None,
        }

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        orderType = type + '_order'
        market = self.market(symbol)
        request = {
            'product_id': market['numericId'],
            # 'limit_price': self.price_to_precision(symbol, price),
            'size': self.amount_to_precision(symbol, amount),
            'side': side,
            'order_type': orderType,
            # 'client_order_id': 'string',
            # 'time_in_force': 'gtc',  # gtc, ioc, fok
            # 'post_only': 'false',  # 'true',
            # 'reduce_only': 'false',  # 'true',
        }
        if type == 'limit':
            request['limit_price'] = self.price_to_precision(symbol, price)
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_order_id')
        params = self.omit(params, ['clientOrderId', 'client_order_id'])
        if clientOrderId is not None:
            request['client_order_id'] = clientOrderId
        response = self.privatePostOrders(self.extend(request, params))
        #
        #     {
        #         "result":{
        #             "average_fill_price":null,
        #             "bracket_order":null,
        #             "bracket_stop_loss_limit_price":null,
        #             "bracket_stop_loss_price":null,
        #             "bracket_take_profit_limit_price":null,
        #             "bracket_take_profit_price":null,
        #             "bracket_trail_amount":null,
        #             "cancellation_reason":null,
        #             "client_order_id":null,
        #             "close_on_trigger":"false",
        #             "commission":"0",
        #             "created_at":"2020-11-16T02:38:26Z",
        #             "id":152870626,
        #             "limit_price":"10000",
        #             "meta_data":{"source":"api"},
        #             "order_type":"limit_order",
        #             "paid_commission":"0",
        #             "product_id":139,
        #             "reduce_only":false,
        #             "side":"buy",
        #             "size":0,
        #             "state":"open",
        #             "stop_order_type":null,
        #             "stop_price":null,
        #             "stop_trigger_method":"mark_price",
        #             "time_in_force":"gtc",
        #             "trail_amount":null,
        #             "unfilled_size":0,
        #             "user_id":22142
        #         },
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_order(result, market)

    def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': int(id),
            'product_id': market['numericId'],
            # 'limit_price': self.price_to_precision(symbol, price),
            # 'size': self.amount_to_precision(symbol, amount),
        }
        if amount is not None:
            request['size'] = int(self.amount_to_precision(symbol, amount))
        if price is not None:
            request['limit_price'] = self.price_to_precision(symbol, price)
        response = self.privatePutOrders(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "id": "ashb1212",
        #             "product_id": 27,
        #             "limit_price": "9200",
        #             "side": "buy",
        #             "size": 100,
        #             "unfilled_size": 50,
        #             "user_id": 1,
        #             "order_type": "limit_order",
        #             "state": "open",
        #             "created_at": "..."
        #         }
        #     }
        #
        result = self.safe_value(response, 'result')
        return self.parse_order(result, market)

    def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': int(id),
            'product_id': market['numericId'],
        }
        response = self.privateDeleteOrders(self.extend(request, params))
        #
        #     {
        #         "result":{
        #             "average_fill_price":null,
        #             "bracket_order":null,
        #             "bracket_stop_loss_limit_price":null,
        #             "bracket_stop_loss_price":null,
        #             "bracket_take_profit_limit_price":null,
        #             "bracket_take_profit_price":null,
        #             "bracket_trail_amount":null,
        #             "cancellation_reason":"cancelled_by_user",
        #             "client_order_id":null,
        #             "close_on_trigger":"false",
        #             "commission":"0",
        #             "created_at":"2020-11-16T02:38:26Z",
        #             "id":152870626,
        #             "limit_price":"10000",
        #             "meta_data":{"source":"api"},
        #             "order_type":"limit_order",
        #             "paid_commission":"0",
        #             "product_id":139,
        #             "reduce_only":false,
        #             "side":"buy",
        #             "size":0,
        #             "state":"cancelled",
        #             "stop_order_type":null,
        #             "stop_price":null,
        #             "stop_trigger_method":"mark_price",
        #             "time_in_force":"gtc",
        #             "trail_amount":null,
        #             "unfilled_size":0,
        #             "user_id":22142
        #         },
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result')
        return self.parse_order(result, market)

    def cancel_all_orders(self, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request = {
            'product_id': market['numericId'],
            # 'cancel_limit_orders': 'true',
            # 'cancel_stop_orders': 'true',
        }
        response = self.privateDeleteOrdersAll(self.extend(request, params))
        #
        #     {
        #         "result":{},
        #         "success":true
        #     }
        #
        return response

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

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

    def fetch_orders_with_method(self, method, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'product_ids': market['id'],  # comma-separated
            # 'contract_types': types,  # comma-separated, futures, perpetual_futures, call_options, put_options, interest_rate_swaps, move_options, spreads
            # 'order_types': types,  # comma-separated, market, limit, stop_market, stop_limit, all_stop
            # 'start_time': since * 1000,
            # 'end_time': self.microseconds(),
            # 'after': string,  # after cursor for pagination
            # 'before': string,  # before cursor for pagination
            # 'page_size': limit,  # number of records per page
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['product_ids'] = market['numericId']  # accepts a comma-separated list of ids
        if since is not None:
            request['start_time'] = str(since) + '000'
        if limit is not None:
            request['page_size'] = limit
        response = getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "id": "ashb1212",
        #                 "product_id": 27,
        #                 "limit_price": "9200",
        #                 "side": "buy",
        #                 "size": 100,
        #                 "unfilled_size": 50,
        #                 "user_id": 1,
        #                 "order_type": "limit_order",
        #                 "state": "open",
        #                 "created_at": "..."
        #             }
        #         ],
        #         "meta": {
        #             "after": "string",
        #             "before": "string"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_orders(result, market, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'product_ids': market['id'],  # comma-separated
            # 'contract_types': types,  # comma-separated, futures, perpetual_futures, call_options, put_options, interest_rate_swaps, move_options, spreads
            # 'start_time': since * 1000,
            # 'end_time': self.microseconds(),
            # 'after': string,  # after cursor for pagination
            # 'before': string,  # before cursor for pagination
            # 'page_size': limit,  # number of records per page
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['product_ids'] = market['numericId']  # accepts a comma-separated list of ids
        if since is not None:
            request['start_time'] = str(since) + '000'
        if limit is not None:
            request['page_size'] = limit
        response = self.privateGetFills(self.extend(request, params))
        #
        #     {
        #         "meta":{
        #             "after":null,
        #             "before":null,
        #             "limit":10,
        #             "total_count":2
        #         },
        #         "result":[
        #             {
        #                 "commission":"0.008335000000000000",
        #                 "created_at":"2020-11-16T19:07:19Z",
        #                 "fill_type":"normal",
        #                 "id":"e7ff05c233a74245b72381f8dd91d1ce",
        #                 "meta_data":{
        #                     "effective_commission_rate":"0.0005",
        #                     "order_price":"16249",
        #                     "order_size":1,
        #                     "order_type":"market_order",
        #                     "order_unfilled_size":0,
        #                     "trading_fee_credits_used":"0"
        #                 },
        #                 "order_id":"152999629",
        #                 "price":"16669",
        #                 "product":{
        #                     "contract_type":"perpetual_futures",
        #                     "contract_unit_currency":"BTC",
        #                     "contract_value":"0.001",
        #                     "id":139,
        #                     "notional_type":"vanilla",
        #                     "quoting_asset":{"minimum_precision":2,"precision":6,"symbol":"USDT"},
        #                     "settling_asset":{"minimum_precision":2,"precision":6,"symbol":"USDT"},
        #                     "symbol":"BTCUSDT",
        #                     "tick_size":"0.5",
        #                     "underlying_asset":{"minimum_precision":4,"precision":8,"symbol":"BTC"}
        #                 },
        #                 "product_id":139,
        #                 "role":"taker",
        #                 "side":"sell",
        #                 "size":1
        #             }
        #         ],
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_trades(result, market, since, limit)

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            # 'asset_id': currency['numericId'],
            # 'end_time': self.seconds(),
            # 'after': 'string',  # after cursor for pagination
            # 'before': 'string',  # before cursor for pagination
            # 'page_size': limit,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['asset_id'] = currency['numericId']
        if limit is not None:
            request['page_size'] = limit
        response = self.privateGetWalletTransactions(self.extend(request, params))
        #
        #     {
        #         "meta":{"after":null,"before":null,"limit":10,"total_count":1},
        #         "result":[
        #             {
        #                 "amount":"29.889184",
        #                 "asset_id":5,
        #                 "balance":"29.889184",
        #                 "created_at":"2020-11-15T21:25:01Z",
        #                 "meta_data":{
        #                     "deposit_id":3884,
        #                     "transaction_id":"0x41a60174849828530abb5008e98fc63c9b598288743ec4ba9620bcce900a3b8d"
        #                 },
        #                 "transaction_type":"deposit",
        #                 "user_id":22142,
        #                 "uuid":"70bb5679da3c4637884e2dc63efaa846"
        #             }
        #         ],
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_ledger(result, currency, since, limit)

    def parse_ledger_entry_type(self, type):
        types = {
            'pnl': 'pnl',
            'deposit': 'transaction',
            'withdrawal': 'transaction',
            'commission': 'fee',
            'conversion': 'trade',
            # 'perpetual_futures_funding': 'perpetual_futures_funding',
            # 'withdrawal_cancellation': 'withdrawal_cancellation',
            'referral_bonus': 'referral',
            'commission_rebate': 'rebate',
            # 'promo_credit': 'promo_credit',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        #     {
        #         "amount":"29.889184",
        #         "asset_id":5,
        #         "balance":"29.889184",
        #         "created_at":"2020-11-15T21:25:01Z",
        #         "meta_data":{
        #             "deposit_id":3884,
        #             "transaction_id":"0x41a60174849828530abb5008e98fc63c9b598288743ec4ba9620bcce900a3b8d"
        #         },
        #         "transaction_type":"deposit",
        #         "user_id":22142,
        #         "uuid":"70bb5679da3c4637884e2dc63efaa846"
        #     }
        #
        id = self.safe_string(item, 'uuid')
        direction = None
        account = None
        metaData = self.safe_value(item, 'meta_data', {})
        referenceId = self.safe_string(metaData, 'transaction_id')
        referenceAccount = None
        type = self.safe_string(item, 'transaction_type')
        if (type == 'deposit') or (type == 'commission_rebate') or (type == 'referral_bonus') or (type == 'pnl') or (type == 'withdrawal_cancellation') or (type == 'promo_credit'):
            direction = 'in'
        elif (type == 'withdrawal') or (type == 'commission') or (type == 'conversion') or (type == 'perpetual_futures_funding'):
            direction = 'out'
        type = self.parse_ledger_entry_type(type)
        currencyId = self.safe_integer(item, 'asset_id')
        currenciesByNumericId = self.safe_value(self.options, 'currenciesByNumericId')
        currency = self.safe_value(currenciesByNumericId, currencyId, currency)
        code = None if (currency is None) else currency['code']
        amount = self.safe_float(item, 'amount')
        timestamp = self.parse8601(self.safe_string(item, 'created_at'))
        after = self.safe_float(item, 'balance')
        before = max(0, after - amount)
        status = 'ok'
        return {
            'info': item,
            'id': id,
            'direction': direction,
            'account': account,
            'referenceId': referenceId,
            'referenceAccount': referenceAccount,
            'type': type,
            'currency': code,
            'amount': amount,
            'before': before,
            'after': after,
            'status': status,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': None,
        }

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'asset_symbol': currency['id'],
        }
        response = self.privateGetDepositsAddress(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "result":{
        #             "id":19628,
        #             "user_id":22142,
        #             "address":"0x0eda26523397534f814d553a065d8e46b4188e9a",
        #             "status":"active",
        #             "updated_at":"2020-11-15T20:25:53.000Z",
        #             "created_at":"2020-11-15T20:25:53.000Z",
        #             "asset_symbol":"USDT",
        #             "custodian":"onc"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        address = self.safe_string(result, 'address')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': None,
            'info': response,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        requestPath = '/' + self.version + '/' + self.implode_params(path, params)
        url = self.urls['api'][api] + requestPath
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        elif api == 'private':
            self.check_required_credentials()
            timestamp = str(self.seconds())
            headers = {
                'api-key': self.apiKey,
                'timestamp': timestamp,
            }
            auth = method + timestamp + requestPath
            if (method == 'GET') or (method == 'DELETE'):
                if query:
                    queryString = '?' + self.urlencode(query)
                    auth += queryString
                    url += queryString
            else:
                body = self.json(query)
                auth += body
                headers['Content-Type'] = 'application/json'
            signature = self.hmac(self.encode(auth), self.encode(self.secret))
            headers['signature'] = signature
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return
        #
        # {"error":{"code":"insufficient_margin","context":{"available_balance":"0.000000000000000000","required_additional_balance":"1.618626000000000000000000000"}},"success":false}
        #
        error = self.safe_value(response, 'error', {})
        errorCode = self.safe_string(error, 'code')
        if errorCode is not None:
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], errorCode, feedback)
            raise ExchangeError(feedback)  # unknown message
