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

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

from ccxt.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
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.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,
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/67149189-df896480-f2b0-11e9-8816-41593e17f9ec.jpg',
                'www': 'https://ftx.com',
                'api': 'https://ftx.com',
                '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,
                'fetchClosedOrders': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingFees': False,
                '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/{future_name}',
                        'futures/{future_name}/stats',
                        'funding_rates',
                        'lt/tokens',
                        'lt/{token_name}',
                    ],
                },
                'private': {
                    'get': [
                        'account',
                        'positions',
                        'wallet/coins',
                        'wallet/balances',
                        'wallet/deposit_address/{coin}',
                        'wallet/deposits',
                        'wallet/withdrawals',
                        'orders',  # ?market={market}
                        'orders/history',  # ?market={market}
                        'orders/{order_id}',
                        'orders/by_client_id/{client_order_id}',
                        'conditional_orders',  # ?market={market}
                        'conditional_orders/history',  # ?market={market}
                        'fills',  # ?market={market}
                        'funding_payments',
                        'lt/balances',
                        'lt/creations',
                        'lt/redemptions',
                        'subaccounts',
                        'subaccounts/{nickname}/balances',
                    ],
                    'post': [
                        'account/leverage',
                        'wallet/withdrawals',
                        'orders',
                        'conditional_orders',
                        'lt/{token_name}/create',
                        'lt/{token_name}/redeem',
                        'subaccounts',
                        'subaccounts/update_name',
                        'subaccounts/transfer',
                    ],
                    'delete': [
                        'orders/{order_id}',
                        'orders/by_client_id/{client_order_id}',
                        'orders',
                        'conditional_orders/{order_id}',
                        'subaccounts',
                    ],
                },
            },
            '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': {
                    '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}
                },
                'broad': {
                    '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,
                    'An unexpected error occurred': ExchangeError,  # {"error":"An unexpected error occurred, please try again later(58BC21C795).","success":false}
                },
            },
            'precisionMode': TICK_SIZE,
        })

    def fetch_currencies(self, params={}):
        response = 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

    def fetch_markets(self, params={}):
        response = 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,
            }
            entry = {
                '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,
            }
            result.append(entry)
        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'))
                symbol = base + '/' + quote
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        last = self.safe_float(ticker, 'last')
        timestamp = 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': None,
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': None,
            '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,
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market_name': market['id'],
        }
        response = 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)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = 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)

    def fetch_order_book(self, symbol, limit=None, params={}):
        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 = 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, timeframe='1m', since=None, limit=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 fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market_name': market['id'],
            'resolution': self.timeframes[timeframe],
        }
        # 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 = 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,
        #         "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"
        #     }
        #
        id = self.safe_string(trade, 'id')
        takerOrMaker = self.safe_string(trade, 'liquidity')
        marketId = self.safe_string(trade, 'market')
        symbol = None
        if marketId is not 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:
            fee = {
                'cost': feeCost,
                '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,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market_name': market['id'],
        }
        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 = 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)

    def fetch_trading_fees(self, params={}):
        self.load_markets()
        response = 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'),
        }

    def fetch_balance(self, params={}):
        self.load_markets()
        response = 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
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # limit orders - fetchOrder, fetchOrders, fetchOpenOrders, createOrder
        #
        #     {
        #         "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
        #     }
        #
        id = self.safe_string(order, 'id')
        timestamp = self.parse8601(self.safe_string(order, 'createdAt'))
        filled = self.safe_float(order, 'filledSize')
        remaining = self.safe_float(order, 'remainingSize')
        symbol = None
        marketId = self.safe_string(order, 'market')
        if marketId in self.markets_by_id:
            market = self.markets_by_id[marketId]
            symbol = market['symbol']
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        status = self.parse_order_status(self.safe_string(order, 'status'))
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'type')
        amount = self.safe_float(order, 'size')
        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'))
        return {
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
        }

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        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
        }
        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 = 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)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            'order_id': int(id),
        }
        response = self.privateDeleteOrdersOrderId(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": "Order queued for cancelation"
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return result

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

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        request = {
            'order_id': id,
        }
        response = self.privateGetOrdersOrderId(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)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market'] = market['id']
        response = self.privateGetOrders(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_orders(result, market, since, limit)

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['market'] = market['id']
        if limit is not None:
            request['limit'] = limit  # default 100, max 100
        if since is not None:
            request['start_time'] = int(since / 1000)
        response = self.privateGetOrdersHistory(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_orders(result, market, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['start_time'] = int(since / 1000)
        response = 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)

    def withdraw(self, code, amount, address, tag=None, params={}):
        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 tag is not None:
            request['tag'] = tag
        response = 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)

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
        }
        response = 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):
        #
        # 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"
        #     }
        #
        code = self.safe_currency_code(self.safe_string(transaction, 'coin'))
        id = self.safe_integer(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')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'tag')
        fee = self.safe_float(transaction, 'fee')
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'addressFrom': None,
            'address': None,
            'addressTo': address,
            'tagFrom': None,
            'tag': tag,
            'tagTo': None,
            'type': None,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'fee': {
                'currency': code,
                'cost': fee,
                'rate': None,
            },
        }

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        response = 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', [])
        return self.parse_transactions(result)

    def fetch_withdrawals(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        response = 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', [])
        return self.parse_transactions(result)

    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))
        url = self.urls['api'] + 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 = {
                'FTX-KEY': self.apiKey,
                'FTX-TS': timestamp,
            }
            if method == 'POST':
                body = self.json(query)
                auth += body
                headers['Content-Type'] = 'application/json'
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
            headers['FTX-SIGN'] = 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
