# -*- 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 PermissionDenied
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 CancelPending
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class ftx(Exchange):

    def describe(self):
        return self.deep_extend(super(ftx, self).describe(), {
            'id': 'ftx',
            'name': 'FTX',
            'countries': ['HK'],
            'rateLimit': 50,
            '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',
                        # spot margin
                        'spot_margin/history',
                    ],
                },
                'private': {
                    'get': [
                        'account',
                        'positions',
                        'wallet/coins',
                        'wallet/balances',
                        'wallet/all_balances',
                        'wallet/deposit_address/{coin}',  # ?method={method}
                        'wallet/deposits',
                        'wallet/withdrawals',
                        'wallet/withdrawal_fee',
                        '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': {
                    'Please slow down': RateLimitExceeded,  # {"error":"Please slow down","success":false}
                    '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}
                    'Size too large': InvalidOrder,  # {"error":"Size too large","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}
                    'Trigger price too high': InvalidOrder,  # {"error":"Trigger price too high","success":false}
                    'Trigger price too low': InvalidOrder,  # {"error":"Trigger price too low","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': BadSymbol,
                    '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}
                    'Only have permissions for subaccount': PermissionDenied,  # {"success":false,"error":"Only have permissions for subaccount *sub_name*"}
                },
            },
            '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},
                },
            }
        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_number(market, 'sizeIncrement')
            priceIncrement = self.safe_number(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_number(ticker, 'last')
        timestamp = self.safe_timestamp(ticker, 'time', self.milliseconds())
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'high'),
            'low': self.safe_number(ticker, 'low'),
            'bid': self.safe_number(ticker, 'bid'),
            'bidVolume': self.safe_number(ticker, 'bidSize'),
            'ask': self.safe_number(ticker, 'ask'),
            'askVolume': self.safe_number(ticker, 'askSize'),
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': self.safe_number(ticker, 'change24h'),
            'average': None,
            'baseVolume': None,
            'quoteVolume': self.safe_number(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)

    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, symbol)

    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_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(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"
        #     }
        #
        #     with -ve fee
        #     {
        #         "id": 1171258927,
        #         "fee": -0.0000713875,
        #         "side": "sell",
        #         "size": 1,
        #         "time": "2021-03-11T13:34:35.523627+00:00",
        #         "type": "order",
        #         "price": 14.2775,
        #         "future": null,
        #         "market": "SOL/USD",
        #         "feeRate": -0.000005,
        #         "orderId": 33182929044,
        #         "tradeId": 582936801,
        #         "liquidity": "maker",
        #         "feeCurrency": "USD",
        #         "baseCurrency": "SOL",
        #         "quoteCurrency": "USD"
        #     }
        #
        #     # from OTC order
        #     {
        #         "id": 1172129651,
        #         "fee": 0,
        #         "side": "sell",
        #         "size": 1.47568846,
        #         "time": "2021-03-11T15:04:46.893383+00:00",
        #         "type": "otc",
        #         "price": 14.60932598,
        #         "future": null,
        #         "market": null,
        #         "feeRate": 0,
        #         "orderId": null,
        #         "tradeId": null,
        #         "liquidity": "taker",
        #         "feeCurrency": "USD",
        #         "baseCurrency": "BCHA",
        #         "quoteCurrency": "USD"
        #     }
        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'))
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'size')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.parse_number(Precise.string_mul(priceString, amountString))
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        side = self.safe_string(trade, 'side')
        fee = None
        feeCost = self.safe_number(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_number(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_number(result, 'makerFee'),
            'taker': self.safe_number(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_string(balance, 'free')
            account['total'] = self.safe_string(balance, 'total')
            result[code] = account
        return self.parse_balance(result, False)

    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_number(order, 'size')
        filled = self.safe_number(order, 'filledSize')
        remaining = self.safe_number(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_number(order, 'avgFillPrice')
        price = self.safe_number_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_number(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'])
        method = None
        if type == 'limit':
            method = 'privatePostOrders'
            request['price'] = float(self.price_to_precision(symbol, price))
        elif type == 'market':
            method = 'privatePostOrders'
            request['price'] = None
        elif (type == 'stop') or (type == 'takeProfit'):
            method = 'privatePostConditionalOrders'
            stopPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
            if stopPrice is None:
                raise ArgumentsRequired(self.id + ' createOrder() requires a stopPrice parameter or a triggerPrice parameter for ' + type + ' orders')
            else:
                params = self.omit(params, ['stopPrice', 'triggerPrice'])
                request['triggerPrice'] = float(self.price_to_precision(symbol, stopPrice))
            if price is not None:
                request['orderPrice'] = float(self.price_to_precision(symbol, price))  # optional, order type is limit if self is specified, otherwise market
        elif type == 'trailingStop':
            method = 'privatePostConditionalOrders'
            request['trailValue'] = float(self.price_to_precision(symbol, price))  # 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_number(params, 'triggerPrice')
        orderPrice = self.safe_number(params, 'orderPrice')
        trailValue = self.safe_number(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 = {}
        # 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()
        request = {
            # 'market': market['id'],  # optional
            # 'conditionalOrdersOnly': False,  # cancel conditional orders only
            # 'limitOrdersOnly': False,  # cancel existing limit orders(non-conditional orders) only
        }
        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, 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
        #
        #     airdrop
        #
        #     {
        #         "id": 9147072,
        #         "coin": "SRM_LOCKED",
        #         "size": 3.12,
        #         "time": "2021-04-27T23:59:03.565983+00:00",
        #         "notes": "SRM Airdrop for FTT holdings",
        #         "status": "complete"
        #     }
        #
        #     regular deposits
        #
        #     {
        #         "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": 'BTC',
        #         "id": 1969806,
        #         "notes": 'Transfer to Dd6gi7m2Eg4zzBbPAxuwfEaHs6tYvyUX5hbPpsTcNPXo',
        #         "size": 0.003,
        #         "status": 'complete',
        #         "time": '2021-02-03T20:28:54.918146+00:00'
        #     }
        #
        code = self.safe_currency_code(self.safe_string(transaction, 'coin'))
        id = self.safe_string(transaction, 'id')
        amount = self.safe_number(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')
        if address is None:
            # parse address from internal transfer
            notes = self.safe_string(transaction, 'notes')
            if (notes is not None) and (notes.find('Transfer to') >= 0):
                address = notes[12:]
        fee = self.safe_number(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
