# -*- 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

# -----------------------------------------------------------------------------

try:
    basestring  # Python 3
except NameError:
    basestring = str  # Python 2
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import CancelPending
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.decimal_to_precision import TICK_SIZE


class ftx(Exchange):

    def describe(self):
        return self.deep_extend(super(ftx, self).describe(), {
            'id': 'ftx',
            'name': 'FTX',
            'countries': ['HK'],
            'rateLimit': 100,
            'certified': True,
            'pro': True,
            'hostname': 'ftx.com',  # or ftx.us
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg',
                'www': 'https://ftx.com',
                'api': {
                    'public': 'https://{hostname}',
                    'private': 'https://{hostname}',
                },
                'doc': 'https://github.com/ftexchange/ftx',
                'fees': 'https://ftexchange.zendesk.com/hc/en-us/articles/360024479432-Fees',
                'referral': 'https://ftx.com/#a=1623029',
            },
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingFees': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFees': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '15s': '15',
                '1m': '60',
                '5m': '300',
                '15m': '900',
                '1h': '3600',
                '4h': '14400',
                '1d': '86400',
            },
            'api': {
                'public': {
                    'get': [
                        'coins',
                        'markets',
                        'markets/{market_name}',
                        'markets/{market_name}/orderbook',  # ?depth={depth}
                        'markets/{market_name}/trades',  # ?limit={limit}&start_time={start_time}&end_time={end_time}
                        'markets/{market_name}/candles',  # ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time}
                        # futures
                        'futures',
                        'futures/{future_name}',
                        'futures/{future_name}/stats',
                        'funding_rates',
                        'indexes/{index_name}/weights',
                        'expired_futures',
                        'indexes/{market_name}/candles',  # ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time}
                        # leverage tokens
                        'lt/tokens',
                        'lt/{token_name}',
                        # options
                        'options/requests',
                        'options/trades',
                        'stats/24h_options_volume',
                        'options/historical_volumes/BTC',
                        'options/open_interest/BTC',
                        'options/historical_open_interest/BTC',
                    ],
                },
                'private': {
                    'get': [
                        'account',
                        'positions',
                        'wallet/coins',
                        'wallet/balances',
                        'wallet/all_balances',
                        'wallet/deposit_address/{coin}',  # ?method={method}
                        'wallet/deposits',
                        'wallet/withdrawals',
                        'wallet/airdrops',
                        'wallet/saved_addresses',
                        'orders',  # ?market={market}
                        'orders/history',  # ?market={market}
                        'orders/{order_id}',
                        'orders/by_client_id/{client_order_id}',
                        'conditional_orders',  # ?market={market}
                        'conditional_orders/{conditional_order_id}/triggers',
                        'conditional_orders/history',  # ?market={market}
                        'spot_margin/borrow_rates',
                        'spot_margin/lending_rates',
                        'spot_margin/borrow_summary',
                        'spot_margin/market_info',  # ?market={market}
                        'spot_margin/borrow_history',
                        'spot_margin/lending_history',
                        'spot_margin/offers',
                        'spot_margin/lending_info',
                        'fills',  # ?market={market}
                        'funding_payments',
                        # leverage tokens
                        'lt/balances',
                        'lt/creations',
                        'lt/redemptions',
                        # subaccounts
                        'subaccounts',
                        'subaccounts/{nickname}/balances',
                        # otc
                        'otc/quotes/{quoteId}',
                        # options
                        'options/my_requests',
                        'options/requests/{request_id}/quotes',
                        'options/my_quotes',
                        'options/account_info',
                        'options/positions',
                        'options/fills',
                        # staking
                        'staking/stakes',
                        'staking/unstake_requests',
                        'staking/balances',
                        'staking/staking_rewards',
                    ],
                    'post': [
                        'account/leverage',
                        'wallet/withdrawals',
                        'wallet/saved_addresses',
                        'orders',
                        'conditional_orders',
                        'orders/{order_id}/modify',
                        'orders/by_client_id/{client_order_id}/modify',
                        'conditional_orders/{order_id}/modify',
                        # spot margin
                        'spot_margin/offers',
                        # leverage tokens
                        'lt/{token_name}/create',
                        'lt/{token_name}/redeem',
                        # subaccounts
                        'subaccounts',
                        'subaccounts/update_name',
                        'subaccounts/transfer',
                        # otc
                        'otc/quotes/{quote_id}/accept',
                        'otc/quotes',
                        # options
                        'options/requests',
                        'options/requests/{request_id}/quotes',
                        'options/quotes/{quote_id}/accept',
                        # staking
                        'staking/unstake_requests',
                        'srm_stakes/stakes',
                    ],
                    'delete': [
                        'wallet/saved_addresses/{saved_address_id}',
                        'orders/{order_id}',
                        'orders/by_client_id/{client_order_id}',
                        'orders',
                        'conditional_orders/{order_id}',
                        # subaccounts
                        'subaccounts',
                        # options
                        'options/requests/{request_id}',
                        'options/quotes/{quote_id}',
                        # staking
                        'staking/unstake_requests/{request_id}',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'maker': 0.02 / 100,
                    'taker': 0.07 / 100,
                    'tiers': {
                        'taker': [
                            [0, 0.07 / 100],
                            [1000000, 0.06 / 100],
                            [5000000, 0.055 / 100],
                            [10000000, 0.05 / 100],
                            [15000000, 0.045 / 100],
                            [35000000, 0.04 / 100],
                        ],
                        'maker': [
                            [0, 0.02 / 100],
                            [1000000, 0.02 / 100],
                            [5000000, 0.015 / 100],
                            [10000000, 0.015 / 100],
                            [15000000, 0.01 / 100],
                            [35000000, 0.01 / 100],
                        ],
                    },
                },
                'funding': {
                    'withdraw': {},
                },
            },
            'exceptions': {
                'exact': {
                    'Size too small for provide': InvalidOrder,  # {"error":"Size too small for provide","success":false}
                    'Not logged in': AuthenticationError,  # {"error":"Not logged in","success":false}
                    'Not enough balances': InsufficientFunds,  # {"error":"Not enough balances","success":false}
                    'InvalidPrice': InvalidOrder,  # {"error":"Invalid price","success":false}
                    'Size too small': InvalidOrder,  # {"error":"Size too small","success":false}
                    'Missing parameter price': InvalidOrder,  # {"error":"Missing parameter price","success":false}
                    'Order not found': OrderNotFound,  # {"error":"Order not found","success":false}
                    'Order already closed': InvalidOrder,  # {"error":"Order already closed","success":false}
                    'Order already queued for cancellation': CancelPending,  # {"error":"Order already queued for cancellation","success":false}
                },
                'broad': {
                    'Account does not have enough margin for order': InsufficientFunds,
                    'Invalid parameter': BadRequest,  # {"error":"Invalid parameter start_time","success":false}
                    'The requested URL was not found on the server': BadRequest,
                    'No such coin': BadRequest,
                    'No such market': BadRequest,
                    'Do not send more than': RateLimitExceeded,
                    'An unexpected error occurred': ExchangeNotAvailable,  # {"error":"An unexpected error occurred, please try again later(58BC21C795).","success":false}
                    'Please retry request': ExchangeNotAvailable,  # {"error":"Please retry request","success":false}
                    'Please try again': ExchangeNotAvailable,  # {"error":"Please try again","success":false}
                },
            },
            'precisionMode': TICK_SIZE,
            'options': {
                # support for canceling conditional orders
                # https://github.com/ccxt/ccxt/issues/6669
                'cancelOrder': {
                    'method': 'privateDeleteOrdersOrderId',  # privateDeleteConditionalOrdersOrderId
                },
                'fetchOpenOrders': {
                    'method': 'privateGetOrders',  # privateGetConditionalOrders
                },
                'fetchOrders': {
                    'method': 'privateGetOrdersHistory',  # privateGetConditionalOrdersHistory
                },
                'sign': {
                    'ftx.com': 'FTX',
                    'ftx.us': 'FTXUS',
                },
            },
        })

    async def fetch_currencies(self, params={}):
        response = await self.publicGetCoins(params)
        currencies = self.safe_value(response, 'result', [])
        #
        #     {
        #         "success":true,
        #         "result": [
        #             {"id":"BTC","name":"Bitcoin"},
        #             {"id":"ETH","name":"Ethereum"},
        #             {"id":"ETHMOON","name":"10X Long Ethereum Token","underlying":"ETH"},
        #             {"id":"EOSBULL","name":"3X Long EOS Token","underlying":"EOS"},
        #         ],
        #     }
        #
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'id')
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'name')
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'type': None,
                'name': name,
                'active': None,
                'fee': None,
                'precision': None,
                'limits': {
                    'withdraw': {'min': None, 'max': None},
                    'amount': {'min': None, 'max': None},
                    'price': {'min': None, 'max': None},
                    'cost': {'min': None, 'max': None},
                },
            }
        return result

    async def fetch_markets(self, params={}):
        response = await self.publicGetMarkets(params)
        #
        #     {
        #         'success': True,
        #         "result": [
        #             {
        #                 "ask":170.37,
        #                 "baseCurrency":null,
        #                 "bid":170.31,
        #                 "change1h":-0.019001554672655036,
        #                 "change24h":-0.024841165359738997,
        #                 "changeBod":-0.03816406029469881,
        #                 "enabled":true,
        #                 "last":170.37,
        #                 "name":"ETH-PERP",
        #                 "price":170.37,
        #                 "priceIncrement":0.01,
        #                 "quoteCurrency":null,
        #                 "quoteVolume24h":7742164.59889,
        #                 "sizeIncrement":0.001,
        #                 "type":"future",
        #                 "underlying":"ETH",
        #                 "volumeUsd24h":7742164.59889
        #             },
        #             {
        #                 "ask":170.44,
        #                 "baseCurrency":"ETH",
        #                 "bid":170.41,
        #                 "change1h":-0.018485459257126403,
        #                 "change24h":-0.023825887743413515,
        #                 "changeBod":-0.037605872388481086,
        #                 "enabled":true,
        #                 "last":172.72,
        #                 "name":"ETH/USD",
        #                 "price":170.44,
        #                 "priceIncrement":0.01,
        #                 "quoteCurrency":"USD",
        #                 "quoteVolume24h":382802.0252,
        #                 "sizeIncrement":0.001,
        #                 "type":"spot",
        #                 "underlying":null,
        #                 "volumeUsd24h":382802.0252
        #             },
        #         ],
        #     }
        #
        result = []
        markets = self.safe_value(response, 'result', [])
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'name')
            baseId = self.safe_string_2(market, 'baseCurrency', 'underlying')
            quoteId = self.safe_string(market, 'quoteCurrency', 'USD')
            type = self.safe_string(market, 'type')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            # check if a market is a spot or future market
            symbol = self.safe_string(market, 'name') if (type == 'future') else (base + '/' + quote)
            active = self.safe_value(market, 'enabled')
            sizeIncrement = self.safe_float(market, 'sizeIncrement')
            priceIncrement = self.safe_float(market, 'priceIncrement')
            precision = {
                'amount': sizeIncrement,
                'price': priceIncrement,
            }
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'type': type,
                'future': (type == 'future'),
                'spot': (type == 'spot'),
                'active': active,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': sizeIncrement,
                        'max': None,
                    },
                    'price': {
                        'min': priceIncrement,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "ask":171.29,
        #         "baseCurrency":null,  # base currency for spot markets
        #         "bid":171.24,
        #         "change1h":-0.0012244897959183673,
        #         "change24h":-0.031603346901854366,
        #         "changeBod":-0.03297013492914808,
        #         "enabled":true,
        #         "last":171.44,
        #         "name":"ETH-PERP",
        #         "price":171.29,
        #         "priceIncrement":0.01,
        #         "quoteCurrency":null,  # quote currency for spot markets
        #         "quoteVolume24h":8570651.12113,
        #         "sizeIncrement":0.001,
        #         "type":"future",
        #         "underlying":"ETH",  # null for spot markets
        #         "volumeUsd24h":8570651.12113,
        #     }
        #
        symbol = None
        marketId = self.safe_string(ticker, 'name')
        if marketId in self.markets_by_id:
            market = self.markets_by_id[marketId]
        else:
            type = self.safe_string(ticker, 'type')
            if type == 'future':
                symbol = marketId
            else:
                base = self.safe_currency_code(self.safe_string(ticker, 'baseCurrency'))
                quote = self.safe_currency_code(self.safe_string(ticker, 'quoteCurrency'))
                if (base is not None) and (quote is not None):
                    symbol = base + '/' + quote
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        last = self.safe_float(ticker, 'last')
        timestamp = self.safe_timestamp(ticker, 'time', self.milliseconds())
        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, 'bid'),
            'bidVolume': self.safe_float(ticker, 'bidSize'),
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': self.safe_float(ticker, 'askSize'),
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': self.safe_float(ticker, 'change24h'),
            'average': None,
            'baseVolume': None,
            'quoteVolume': self.safe_float(ticker, 'quoteVolume24h'),
            'info': ticker,
        }

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_name': market['id'],
        }
        response = await self.publicGetMarketsMarketName(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "result":{
        #             "ask":171.29,
        #             "baseCurrency":null,  # base currency for spot markets
        #             "bid":171.24,
        #             "change1h":-0.0012244897959183673,
        #             "change24h":-0.031603346901854366,
        #             "changeBod":-0.03297013492914808,
        #             "enabled":true,
        #             "last":171.44,
        #             "name":"ETH-PERP",
        #             "price":171.29,
        #             "priceIncrement":0.01,
        #             "quoteCurrency":null,  # quote currency for spot markets
        #             "quoteVolume24h":8570651.12113,
        #             "sizeIncrement":0.001,
        #             "type":"future",
        #             "underlying":"ETH",  # null for spot markets
        #             "volumeUsd24h":8570651.12113,
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_ticker(result, market)

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

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        response = await self.publicGetMarkets(params)
        #
        #     {
        #         'success': True,
        #         "result": [
        #             {
        #                 "ask":170.44,
        #                 "baseCurrency":"ETH",
        #                 "bid":170.41,
        #                 "change1h":-0.018485459257126403,
        #                 "change24h":-0.023825887743413515,
        #                 "changeBod":-0.037605872388481086,
        #                 "enabled":true,
        #                 "last":172.72,
        #                 "name":"ETH/USD",
        #                 "price":170.44,
        #                 "priceIncrement":0.01,
        #                 "quoteCurrency":"USD",
        #                 "quoteVolume24h":382802.0252,
        #                 "sizeIncrement":0.001,
        #                 "type":"spot",
        #                 "underlying":null,
        #                 "volumeUsd24h":382802.0252
        #             },
        #         ],
        #     }
        #
        tickers = self.safe_value(response, 'result', [])
        return self.parse_tickers(tickers, symbols)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market_name': market['id'],
        }
        if limit is not None:
            request['depth'] = limit  # max 100, default 20
        response = await self.publicGetMarketsMarketNameOrderbook(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "result":{
        #             "asks":[
        #                 [171.95,279.865],
        #                 [171.98,102.42],
        #                 [171.99,124.11],
        #             ],
        #             "bids":[
        #                 [171.93,69.749],
        #                 [171.9,288.325],
        #                 [171.88,87.47],
        #             ],
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_order_book(result)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "close":177.23,
        #         "high":177.45,
        #         "low":177.2,
        #         "open":177.43,
        #         "startTime":"2019-10-17T13:27:00+00:00",
        #         "time":1571318820000.0,
        #         "volume":0.0
        #     }
        #
        return [
            self.safe_integer(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 get_market_id(self, symbol, key, params={}):
        parts = self.get_market_params(symbol, key, params)
        return self.safe_string(parts, 1, symbol)

    def get_market_params(self, symbol, key, params={}):
        market = None
        marketId = None
        if symbol in self.markets:
            market = self.market(symbol)
            marketId = market['id']
        else:
            marketId = self.safe_string(params, key, symbol)
        return [market, marketId]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market, marketId = self.get_market_params(symbol, 'market_name', params)
        request = {
            'resolution': self.timeframes[timeframe],
            'market_name': marketId,
        }
        # max 1501 candles, including the current candle when since is not specified
        limit = 1501 if (limit is None) else limit
        if since is None:
            request['end_time'] = self.seconds()
            request['limit'] = limit
            request['start_time'] = request['end_time'] - limit * self.parse_timeframe(timeframe)
        else:
            request['start_time'] = int(since / 1000)
            request['limit'] = limit
            request['end_time'] = self.sum(request['start_time'], limit * self.parse_timeframe(timeframe))
        response = await self.publicGetMarketsMarketNameCandles(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result":[
        #             {
        #                 "close":177.23,
        #                 "high":177.45,
        #                 "low":177.2,
        #                 "open":177.43,
        #                 "startTime":"2019-10-17T13:27:00+00:00",
        #                 "time":1571318820000.0,
        #                 "volume":0.0
        #             },
        #             {
        #                 "close":177.26,
        #                 "high":177.33,
        #                 "low":177.23,
        #                 "open":177.23,
        #                 "startTime":"2019-10-17T13:28:00+00:00",
        #                 "time":1571318880000.0,
        #                 "volume":0.0
        #             },
        #         ],
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_ohlcvs(result, market, timeframe, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "id":1715826,
        #         "liquidation":false,
        #         "price":171.62,
        #         "side":"buy",
        #         "size":2.095,
        #         "time":"2019-10-18T12:59:54.288166+00:00"
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "fee": 20.1374935,
        #         "feeRate": 0.0005,
        #         "feeCurrency": "USD",
        #         "future": "EOS-0329",
        #         "id": 11215,
        #         "liquidity": "taker",
        #         "market": "EOS-0329",
        #         "baseCurrency": null,
        #         "quoteCurrency": null,
        #         "orderId": 8436981,
        #         "price": 4.201,
        #         "side": "buy",
        #         "size": 9587,
        #         "time": "2019-03-27T19:15:10.204619+00:00",
        #         "type": "order"
        #     }
        #
        #     {
        #         "baseCurrency": "BTC",
        #         "fee": 0,
        #         "feeCurrency": "USD",
        #         "feeRate": 0,
        #         "future": null,
        #         "id": 664079556,
        #         "liquidity": "taker",
        #         "market": null,
        #         "orderId": null,
        #         "price": 34830.61359,
        #         "quoteCurrency": "USD",
        #         "side": "sell",
        #         "size": 0.0005996,
        #         "time": "2021-01-15T16:05:29.246135+00:00",
        #         "tradeId": null,
        #         "type": "otc"
        #     }
        #
        id = self.safe_string(trade, 'id')
        takerOrMaker = self.safe_string(trade, 'liquidity')
        marketId = self.safe_string(trade, 'market')
        symbol = None
        if marketId in self.markets_by_id:
            market = self.markets_by_id[marketId]
            symbol = market['symbol']
        else:
            base = self.safe_currency_code(self.safe_string(trade, 'baseCurrency'))
            quote = self.safe_currency_code(self.safe_string(trade, 'quoteCurrency'))
            if (base is not None) and (quote is not None):
                symbol = base + '/' + quote
            else:
                symbol = marketId
        timestamp = self.parse8601(self.safe_string(trade, 'time'))
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'size')
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        side = self.safe_string(trade, 'side')
        cost = None
        if price is not None and amount is not None:
            cost = price * amount
        fee = None
        feeCost = self.safe_float(trade, 'fee')
        if feeCost is not None:
            feeCurrencyId = self.safe_string(trade, 'feeCurrency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
                'rate': self.safe_float(trade, 'feeRate'),
            }
        orderId = self.safe_string(trade, 'orderId')
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market, marketId = self.get_market_params(symbol, 'market_name', params)
        request = {
            'market_name': marketId,
        }
        if since is not None:
            request['start_time'] = int(since / 1000)
            # start_time doesn't work without end_time
            request['end_time'] = self.seconds()
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetMarketsMarketNameTrades(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "result":[
        #             {
        #                 "id":1715826,
        #                 "liquidation":false,
        #                 "price":171.62,
        #                 "side":"buy",
        #                 "size":2.095,
        #                 "time":"2019-10-18T12:59:54.288166+00:00"
        #             },
        #             {
        #                 "id":1715763,
        #                 "liquidation":false,
        #                 "price":171.89,
        #                 "side":"sell",
        #                 "size":1.477,
        #                 "time":"2019-10-18T12:58:38.443734+00:00"
        #             },
        #         ],
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_trades(result, market, since, limit)

    async def fetch_trading_fees(self, params={}):
        await self.load_markets()
        response = await self.privateGetAccount(params)
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "backstopProvider": True,
        #             "collateral": 3568181.02691129,
        #             "freeCollateral": 1786071.456884368,
        #             "initialMarginRequirement": 0.12222384240257728,
        #             "liquidating": False,
        #             "maintenanceMarginRequirement": 0.07177992558058484,
        #             "makerFee": 0.0002,
        #             "marginFraction": 0.5588433331419503,
        #             "openMarginFraction": 0.2447194090423075,
        #             "takerFee": 0.0005,
        #             "totalAccountValue": 3568180.98341129,
        #             "totalPositionSize": 6384939.6992,
        #             "username": "user@domain.com",
        #             "positions": [
        #                 {
        #                     "cost": -31.7906,
        #                     "entryPrice": 138.22,
        #                     "future": "ETH-PERP",
        #                     "initialMarginRequirement": 0.1,
        #                     "longOrderSize": 1744.55,
        #                     "maintenanceMarginRequirement": 0.04,
        #                     "netSize": -0.23,
        #                     "openSize": 1744.32,
        #                     "realizedPnl": 3.39441714,
        #                     "shortOrderSize": 1732.09,
        #                     "side": "sell",
        #                     "size": 0.23,
        #                     "unrealizedPnl": 0,
        #                 },
        #             ],
        #         },
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return {
            'info': response,
            'maker': self.safe_float(result, 'makerFee'),
            'taker': self.safe_float(result, 'takerFee'),
        }

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetWalletBalances(params)
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "coin": "USDTBEAR",
        #                 "free": 2320.2,
        #                 "total": 2340.2
        #             },
        #         ],
        #     }
        #
        result = {
            'info': response,
        }
        balances = self.safe_value(response, 'result', [])
        for i in range(0, len(balances)):
            balance = balances[i]
            code = self.safe_currency_code(self.safe_string(balance, 'coin'))
            account = self.account()
            account['free'] = self.safe_float(balance, 'free')
            account['total'] = self.safe_float(balance, 'total')
            result[code] = account
        return self.parse_balance(result)

    def parse_order_status(self, status):
        statuses = {
            'new': 'open',
            'open': 'open',
            'closed': 'closed',  # filled or canceled
            'triggered': 'closed',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # limit orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder, editOrder
        #
        #     {
        #         "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #         "filledSize": 0,
        #         "future": "XRP-PERP",
        #         "id": 9596912,
        #         "market": "XRP-PERP",
        #         "price": 0.306525,
        #         "remainingSize": 31431,
        #         "side": "sell",
        #         "size": 31431,
        #         "status": "open",
        #         "type": "limit",
        #         "reduceOnly": False,
        #         "ioc": False,
        #         "postOnly": False,
        #         "clientId": null,
        #     }
        #
        # market orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder
        #
        #     {
        #         "avgFillPrice": 2666.0,
        #         "clientId": None,
        #         "createdAt": "2020-02-12T00: 53: 49.009726+00: 00",
        #         "filledSize": 0.0007,
        #         "future": None,
        #         "id": 3109208514,
        #         "ioc": True,
        #         "market": "BNBBULL/USD",
        #         "postOnly": False,
        #         "price": None,
        #         "reduceOnly": False,
        #         "remainingSize": 0.0,
        #         "side": "buy",
        #         "size": 0.0007,
        #         "status": "closed",
        #         "type": "market"
        #     }
        #
        # createOrder(conditional, "stop", "trailingStop", or "takeProfit")
        #
        #     {
        #         "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #         "future": "XRP-PERP",
        #         "id": 9596912,
        #         "market": "XRP-PERP",
        #         "triggerPrice": 0.306525,
        #         "orderId": null,
        #         "side": "sell",
        #         "size": 31431,
        #         "status": "open",
        #         "type": "stop",
        #         "orderPrice": null,
        #         "error": null,
        #         "triggeredAt": null,
        #         "reduceOnly": False
        #     }
        #
        # editOrder(conditional, stop, trailing stop, take profit)
        #
        #     {
        #         "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #         "future": "XRP-PERP",
        #         "id": 9596912,
        #         "market": "XRP-PERP",
        #         "triggerPrice": 0.306225,
        #         "orderId": null,
        #         "side": "sell",
        #         "size": 31431,
        #         "status": "open",
        #         "type": "stop",
        #         "orderPrice": null,
        #         "error": null,
        #         "triggeredAt": null,
        #         "reduceOnly": False,
        #         "orderType": "market",
        #         "filledSize": 0,
        #         "avgFillPrice": null,
        #         "retryUntilFilled": False
        #     }
        #
        # canceled order with a closed status
        #
        #     {
        #         "avgFillPrice":null,
        #         "clientId":null,
        #         "createdAt":"2020-09-01T13:45:57.119695+00:00",
        #         "filledSize":0.0,
        #         "future":null,
        #         "id":8553541288,
        #         "ioc":false,
        #         "liquidation":false,
        #         "market":"XRP/USDT",
        #         "postOnly":false,
        #         "price":0.5,
        #         "reduceOnly":false,
        #         "remainingSize":0.0,
        #         "side":"sell",
        #         "size":46.0,
        #         "status":"closed",
        #         "type":"limit"
        #     }
        #
        id = self.safe_string(order, 'id')
        timestamp = self.parse8601(self.safe_string(order, 'createdAt'))
        status = self.parse_order_status(self.safe_string(order, 'status'))
        amount = self.safe_float(order, 'size')
        filled = self.safe_float(order, 'filledSize')
        remaining = self.safe_float(order, 'remainingSize')
        if (remaining == 0.0) and (amount is not None) and (filled is not None):
            remaining = max(amount - filled, 0)
            if remaining > 0:
                status = 'canceled'
        symbol = None
        marketId = self.safe_string(order, 'market')
        if marketId is not None:
            if marketId in self.markets_by_id:
                market = self.markets_by_id[marketId]
                symbol = market['symbol']
            else:
                # support for delisted market ids
                # https://github.com/ccxt/ccxt/issues/7113
                symbol = marketId
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'type')
        average = self.safe_float(order, 'avgFillPrice')
        price = self.safe_float_2(order, 'price', 'triggerPrice', average)
        cost = None
        if filled is not None and price is not None:
            cost = filled * price
        lastTradeTimestamp = self.parse8601(self.safe_string(order, 'triggeredAt'))
        clientOrderId = self.safe_string(order, 'clientId')
        stopPrice = self.safe_float(order, 'triggerPrice')
        postOnly = self.safe_value(order, 'postOnly')
        return {
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': postOnly,
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
        }

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
            'side': side,  # "buy" or "sell"
            # 'price': 0.306525,  # send null for market orders
            'type': type,  # "limit", "market", "stop", "trailingStop", or "takeProfit"
            'size': float(self.amount_to_precision(symbol, amount)),
            # 'reduceOnly': False,  # optional, default is False
            # 'ioc': False,  # optional, default is False, limit or market orders only
            # 'postOnly': False,  # optional, default is False, limit or market orders only
            # 'clientId': 'abcdef0123456789',  # string, optional, client order id, limit or market orders only
        }
        clientOrderId = self.safe_string_2(params, 'clientId', 'clientOrderId')
        if clientOrderId is not None:
            request['clientId'] = clientOrderId
            params = self.omit(params, ['clientId', 'clientOrderId'])
        priceToPrecision = None
        if price is not None:
            priceToPrecision = float(self.price_to_precision(symbol, price))
        method = 'privatePostConditionalOrders'
        if type == 'limit':
            method = 'privatePostOrders'
            request['price'] = priceToPrecision
        elif type == 'market':
            method = 'privatePostOrders'
            request['price'] = None
        elif (type == 'stop') or (type == 'takeProfit'):
            request['triggerPrice'] = priceToPrecision
            # request['orderPrice'] = number  # optional, order type is limit if self is specified, otherwise market
        elif type == 'trailingStop':
            request['trailValue'] = priceToPrecision  # negative for "sell", positive for "buy"
        else:
            raise InvalidOrder(self.id + ' createOrder() does not support order type ' + type + ', only limit, market, stop, trailingStop, or takeProfit orders are supported')
        response = await getattr(self, method)(self.extend(request, params))
        #
        # orders
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #                 "filledSize": 0,
        #                 "future": "XRP-PERP",
        #                 "id": 9596912,
        #                 "market": "XRP-PERP",
        #                 "price": 0.306525,
        #                 "remainingSize": 31431,
        #                 "side": "sell",
        #                 "size": 31431,
        #                 "status": "open",
        #                 "type": "limit",
        #                 "reduceOnly": False,
        #                 "ioc": False,
        #                 "postOnly": False,
        #                 "clientId": null,
        #             }
        #         ]
        #     }
        #
        # conditional orders
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #                 "future": "XRP-PERP",
        #                 "id": 9596912,
        #                 "market": "XRP-PERP",
        #                 "triggerPrice": 0.306525,
        #                 "orderId": null,
        #                 "side": "sell",
        #                 "size": 31431,
        #                 "status": "open",
        #                 "type": "stop",
        #                 "orderPrice": null,
        #                 "error": null,
        #                 "triggeredAt": null,
        #                 "reduceOnly": False
        #             }
        #         ]
        #     }
        #
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_order(result, market)

    async def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {}
        method = None
        clientOrderId = self.safe_string_2(params, 'client_order_id', 'clientOrderId')
        triggerPrice = self.safe_float(params, 'triggerPrice')
        orderPrice = self.safe_float(params, 'orderPrice')
        trailValue = self.safe_float(params, 'trailValue')
        params = self.omit(params, ['client_order_id', 'clientOrderId', 'triggerPrice', 'orderPrice', 'trailValue'])
        triggerPriceIsDefined = (triggerPrice is not None)
        orderPriceIsDefined = (orderPrice is not None)
        trailValueIsDefined = (trailValue is not None)
        if triggerPriceIsDefined or orderPriceIsDefined or trailValueIsDefined:
            method = 'privatePostConditionalOrdersOrderIdModify'
            request['order_id'] = id
            if triggerPriceIsDefined:
                request['triggerPrice'] = float(self.price_to_precision(symbol, triggerPrice))
            if orderPriceIsDefined:
                # only for stop limit or take profit limit orders
                request['orderPrice'] = float(self.price_to_precision(symbol, orderPrice))
            if trailValueIsDefined:
                # negative for sell orders, positive for buy orders
                request['trailValue'] = float(self.price_to_precision(symbol, trailValue))
        else:
            if clientOrderId is None:
                method = 'privatePostOrdersOrderIdModify'
                request['order_id'] = id
            else:
                method = 'privatePostOrdersByClientIdClientOrderIdModify'
                request['client_order_id'] = clientOrderId
                # request['clientId'] = clientOrderId
            if price is not None:
                request['price'] = float(self.price_to_precision(symbol, price))
        if amount is not None:
            request['size'] = float(self.amount_to_precision(symbol, amount))
        response = await getattr(self, method)(self.extend(request, params))
        #
        # regular order
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "createdAt": "2019-03-05T11:56:55.728933+00:00",
        #             "filledSize": 0,
        #             "future": "XRP-PERP",
        #             "id": 9596932,
        #             "market": "XRP-PERP",
        #             "price": 0.326525,
        #             "remainingSize": 31431,
        #             "side": "sell",
        #             "size": 31431,
        #             "status": "open",
        #             "type": "limit",
        #             "reduceOnly": False,
        #             "ioc": False,
        #             "postOnly": False,
        #             "clientId": null,
        #         }
        #     }
        #
        # conditional trigger order
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #             "future": "XRP-PERP",
        #             "id": 9596912,
        #             "market": "XRP-PERP",
        #             "triggerPrice": 0.306225,
        #             "orderId": null,
        #             "side": "sell",
        #             "size": 31431,
        #             "status": "open",
        #             "type": "stop",
        #             "orderPrice": null,
        #             "error": null,
        #             "triggeredAt": null,
        #             "reduceOnly": False,
        #             "orderType": "market",
        #             "filledSize": 0,
        #             "avgFillPrice": null,
        #             "retryUntilFilled": False
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_order(result, market)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'order_id': int(id),
        }
        # support for canceling conditional orders
        # https://github.com/ccxt/ccxt/issues/6669
        options = self.safe_value(self.options, 'cancelOrder', {})
        defaultMethod = self.safe_string(options, 'method', 'privateDeleteOrdersOrderId')
        method = self.safe_string(params, 'method', defaultMethod)
        type = self.safe_value(params, 'type')
        clientOrderId = self.safe_value_2(params, 'client_order_id', 'clientOrderId')
        if clientOrderId is None:
            request['order_id'] = int(id)
            if (type == 'stop') or (type == 'trailingStop') or (type == 'takeProfit'):
                method = 'privateDeleteConditionalOrdersOrderId'
        else:
            request['client_order_id'] = clientOrderId
            method = 'privateDeleteOrdersByClientIdClientOrderId'
        query = self.omit(params, ['method', 'type', 'client_order_id', 'clientOrderId'])
        response = await getattr(self, method)(self.extend(request, query))
        #
        #     {
        #         "success": True,
        #         "result": "Order queued for cancelation"
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return result

    async def cancel_all_orders(self, symbol=None, params={}):
        await self.load_markets()
        conditionalOrdersOnly = self.safe_value(params, 'conditionalOrdersOnly')
        request = {
            # 'market': market['id'],  # optional
            # 'conditionalOrdersOnly': False,  # cancel conditional orders only
            # 'limitOrdersOnly': False,  # cancel existing limit orders(non-conditional orders) only
        }
        if conditionalOrdersOnly:
            request['conditionalOrdersOnly'] = conditionalOrdersOnly
        else:
            request['limitOrdersOnly'] = True
        marketId = self.get_market_id(symbol, 'market', params)
        if marketId is not None:
            request['market'] = marketId
        response = await self.privateDeleteOrders(self.extend(request, params))
        result = self.safe_value(response, 'result', {})
        #
        #     {
        #         "success": True,
        #         "result": "Orders queued for cancelation"
        #     }
        #
        return result

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {}
        clientOrderId = self.safe_value_2(params, 'client_order_id', 'clientOrderId')
        method = 'privateGetOrdersOrderId'
        if clientOrderId is None:
            request['order_id'] = id
        else:
            request['client_order_id'] = clientOrderId
            params = self.omit(params, ['client_order_id', 'clientOrderId'])
            method = 'privateGetOrdersByClientIdClientOrderId'
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #             "filledSize": 10,
        #             "future": "XRP-PERP",
        #             "id": 9596912,
        #             "market": "XRP-PERP",
        #             "price": 0.306525,
        #             "avgFillPrice": 0.306526,
        #             "remainingSize": 31421,
        #             "side": "sell",
        #             "size": 31431,
        #             "status": "open",
        #             "type": "limit",
        #             "reduceOnly": False,
        #             "ioc": False,
        #             "postOnly": False,
        #             "clientId": null
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_order(result)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        market, marketId = self.get_market_params(symbol, 'market', params)
        if marketId is not None:
            request['market'] = marketId
        # support for canceling conditional orders
        # https://github.com/ccxt/ccxt/issues/6669
        options = self.safe_value(self.options, 'fetchOpenOrders', {})
        defaultMethod = self.safe_string(options, 'method', 'privateGetOrders')
        method = self.safe_string(params, 'method', defaultMethod)
        type = self.safe_value(params, 'type')
        if (type == 'stop') or (type == 'trailingStop') or (type == 'takeProfit'):
            method = 'privateGetConditionalOrders'
        query = self.omit(params, ['method', 'type'])
        response = await getattr(self, method)(self.extend(request, query))
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #                 "filledSize": 10,
        #                 "future": "XRP-PERP",
        #                 "id": 9596912,
        #                 "market": "XRP-PERP",
        #                 "price": 0.306525,
        #                 "avgFillPrice": 0.306526,
        #                 "remainingSize": 31421,
        #                 "side": "sell",
        #                 "size": 31431,
        #                 "status": "open",
        #                 "type": "limit",
        #                 "reduceOnly": False,
        #                 "ioc": False,
        #                 "postOnly": False,
        #                 "clientId": null
        #             }
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_orders(result, market, since, limit)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        market, marketId = self.get_market_params(symbol, 'market', params)
        if marketId is not None:
            request['market'] = marketId
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        if since is not None:
            request['start_time'] = int(since / 1000)
        # support for canceling conditional orders
        # https://github.com/ccxt/ccxt/issues/6669
        options = self.safe_value(self.options, 'fetchOrders', {})
        defaultMethod = self.safe_string(options, 'method', 'privateGetOrdersHistory')
        method = self.safe_string(params, 'method', defaultMethod)
        type = self.safe_value(params, 'type')
        if (type == 'stop') or (type == 'trailingStop') or (type == 'takeProfit'):
            method = 'privateGetConditionalOrdersHistory'
        query = self.omit(params, ['method', 'type'])
        response = await getattr(self, method)(self.extend(request, query))
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "createdAt": "2019-03-05T09:56:55.728933+00:00",
        #                 "filledSize": 10,
        #                 "future": "XRP-PERP",
        #                 "id": 9596912,
        #                 "market": "XRP-PERP",
        #                 "price": 0.306525,
        #                 "avgFillPrice": 0.306526,
        #                 "remainingSize": 31421,
        #                 "side": "sell",
        #                 "size": 31431,
        #                 "status": "open",
        #                 "type": "limit",
        #                 "reduceOnly": False,
        #                 "ioc": False,
        #                 "postOnly": False,
        #                 "clientId": null
        #             }
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_orders(result, market, since, limit)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market, marketId = self.get_market_params(symbol, 'market', params)
        request = {}
        if marketId is not None:
            request['market'] = marketId
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['start_time'] = int(since / 1000)
            request['end_time'] = self.seconds()
        response = await self.privateGetFills(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "fee": 20.1374935,
        #                 "feeRate": 0.0005,
        #                 "future": "EOS-0329",
        #                 "id": 11215,
        #                 "liquidity": "taker",
        #                 "market": "EOS-0329",
        #                 "baseCurrency": null,
        #                 "quoteCurrency": null,
        #                 "orderId": 8436981,
        #                 "price": 4.201,
        #                 "side": "buy",
        #                 "size": 9587,
        #                 "time": "2019-03-27T19:15:10.204619+00:00",
        #                 "type": "order"
        #             }
        #         ]
        #     }
        #
        trades = self.safe_value(response, 'result', [])
        return self.parse_trades(trades, market, since, limit)

    async def withdraw(self, code, amount, address, tag=None, params={}):
        await self.load_markets()
        self.check_address(address)
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
            'size': amount,
            'address': address,
            # 'password': 'string',  # optional withdrawal password if it is required for your account
            # 'code': '192837',  # optional 2fa code if it is required for your account
        }
        if self.password is not None:
            request['password'] = self.password
        if tag is not None:
            request['tag'] = tag
        response = await self.privatePostWalletWithdrawals(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "coin": "USDTBEAR",
        #             "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
        #             "tag": "null",
        #             "fee": 0,
        #             "id": 1,
        #             "size": "20.2",
        #             "status": "requested",
        #             "time": "2019-03-05T09:56:55.728933+00:00",
        #             "txid": "null"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_transaction(result, currency)

    async def fetch_positions(self, symbols=None, since=None, limit=None, params={}):
        await self.load_markets()
        response = await self.privateGetAccount(params)
        #
        #     {
        #         "result":{
        #             "backstopProvider":false,
        #             "chargeInterestOnNegativeUsd":false,
        #             "collateral":2830.2567913677476,
        #             "freeCollateral":2829.670741867416,
        #             "initialMarginRequirement":0.05,
        #             "leverage":20.0,
        #             "liquidating":false,
        #             "maintenanceMarginRequirement":0.03,
        #             "makerFee":0.0,
        #             "marginFraction":null,
        #             "openMarginFraction":null,
        #             "positionLimit":null,
        #             "positionLimitUsed":null,
        #             "positions":[
        #                 {
        #                     "collateralUsed":0.0,
        #                     "cost":0.0,
        #                     "entryPrice":null,
        #                     "estimatedLiquidationPrice":null,
        #                     "future":"XRP-PERP",
        #                     "initialMarginRequirement":0.05,
        #                     "longOrderSize":0.0,
        #                     "maintenanceMarginRequirement":0.03,
        #                     "netSize":0.0,
        #                     "openSize":0.0,
        #                     "realizedPnl":0.016,
        #                     "shortOrderSize":0.0,
        #                     "side":"buy",
        #                     "size":0.0,
        #                     "unrealizedPnl":0.0,
        #                 }
        #             ],
        #             "spotLendingEnabled":false,
        #             "spotMarginEnabled":false,
        #             "takerFee":0.0007,
        #             "totalAccountValue":2830.2567913677476,
        #             "totalPositionSize":0.0,
        #             "useFttCollateral":true,
        #             "username":"igor.kroitor@gmail.com"
        #         },
        #         "success":true
        #     }
        #
        result = self.safe_value(response, 'result', {})
        # todo unify parsePosition/parsePositions
        return self.safe_value(result, 'positions', [])

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
        }
        response = await self.privateGetWalletDepositAddressCoin(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
        #             "tag": "null"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        address = self.safe_string(result, 'address')
        tag = self.safe_string(result, 'tag')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': response,
        }

    def parse_transaction_status(self, status):
        statuses = {
            # what are other statuses here?
            'confirmed': 'ok',  # deposits
            'complete': 'ok',  # withdrawals
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #         "coin": "TUSD",
        #         "confirmations": 64,
        #         "confirmedTime": "2019-03-05T09:56:55.728933+00:00",
        #         "fee": 0,
        #         "id": 1,
        #         "sentTime": "2019-03-05T09:56:55.735929+00:00",
        #         "size": "99.0",
        #         "status": "confirmed",
        #         "time": "2019-03-05T09:56:55.728933+00:00",
        #         "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "coin": "TUSD",
        #         "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
        #         "tag": "null",
        #         "fee": 0,
        #         "id": 1,
        #         "size": "99.0",
        #         "status": "complete",
        #         "time": "2019-03-05T09:56:55.728933+00:00",
        #         "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
        #     }
        #
        #     {
        #         'coin': 'USD',
        #         'id': '503722',
        #         'notes': 'Transfer',
        #         'size': '3.35',
        #         'status': 'complete',
        #         'time': '2020-10-06T03:20:34.201556+00:00',
        #     }
        #
        code = self.safe_currency_code(self.safe_string(transaction, 'coin'))
        id = self.safe_string(transaction, 'id')
        amount = self.safe_float(transaction, 'size')
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        timestamp = self.parse8601(self.safe_string(transaction, 'time'))
        txid = self.safe_string(transaction, 'txid')
        tag = None
        address = self.safe_value(transaction, 'address')
        if not isinstance(address, basestring):
            tag = self.safe_string(address, 'tag')
            address = self.safe_string(address, 'address')
        fee = self.safe_float(transaction, 'fee')
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'addressFrom': None,
            'address': address,
            'addressTo': address,
            'tagFrom': None,
            'tag': tag,
            'tagTo': tag,
            'type': None,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'fee': {
                'currency': code,
                'cost': fee,
                'rate': None,
            },
        }

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        response = await self.privateGetWalletDeposits(params)
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "coin": "TUSD",
        #             "confirmations": 64,
        #             "confirmedTime": "2019-03-05T09:56:55.728933+00:00",
        #             "fee": 0,
        #             "id": 1,
        #             "sentTime": "2019-03-05T09:56:55.735929+00:00",
        #             "size": "99.0",
        #             "status": "confirmed",
        #             "time": "2019-03-05T09:56:55.728933+00:00",
        #             "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', [])
        currency = None
        if code is not None:
            currency = self.currency(code)
        return self.parse_transactions(result, currency, since, limit, {'type': 'deposit'})

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        response = await self.privateGetWalletWithdrawals(params)
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "coin": "TUSD",
        #             "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
        #             "tag": "null",
        #             "fee": 0,
        #             "id": 1,
        #             "size": "99.0",
        #             "status": "complete",
        #             "time": "2019-03-05T09:56:55.728933+00:00",
        #             "txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', [])
        currency = None
        if code is not None:
            currency = self.currency(code)
        return self.parse_transactions(result, currency, since, limit, {'type': 'withdrawal'})

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        request = '/api/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        baseUrl = self.implode_params(self.urls['api'][api], {'hostname': self.hostname})
        url = baseUrl + request
        if method != 'POST':
            if query:
                suffix = '?' + self.urlencode(query)
                url += suffix
                request += suffix
        if api == 'private':
            self.check_required_credentials()
            timestamp = str(self.milliseconds())
            auth = timestamp + method + request
            headers = {}
            if (method == 'POST') or (method == 'DELETE'):
                body = self.json(query)
                auth += body
                headers['Content-Type'] = 'application/json'
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
            options = self.safe_value(self.options, 'sign', {})
            headerPrefix = self.safe_string(options, self.hostname, 'FTX')
            keyField = headerPrefix + '-KEY'
            tsField = headerPrefix + '-TS'
            signField = headerPrefix + '-SIGN'
            headers[keyField] = self.apiKey
            headers[tsField] = timestamp
            headers[signField] = 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  # fallback to the default error handler
        #
        #     {"error":"Invalid parameter start_time","success":false}
        #     {"error":"Not enough balances","success":false}
        #
        success = self.safe_value(response, 'success')
        if not success:
            feedback = self.id + ' ' + body
            error = self.safe_string(response, 'error')
            self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], error, feedback)
            raise ExchangeError(feedback)  # unknown message
