# -*- 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 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 BadResponse
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 DuplicateOrderId
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': ['BS'],  # Bahamas
            #  hard limit of 7 requests per 200ms => 35 requests per 1000ms => 1000ms / 35 = 28.5714 ms between requests
            # 10 withdrawal requests per 30 seconds = (1000ms / rateLimit) / (1/3) = 90.1
            # cancels do not count towards rateLimit
            # only 'order-making' requests count towards ratelimit
            'rateLimit': 28.57,
            'certified': False,
            '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': {
                    'url': 'https://ftx.com/referrals#a=1623029',
                    'discount': 0.05,
                },
            },
            'has': {
                'CORS': None,
                'spot': True,
                'margin': True,
                'swap': True,
                'future': True,
                'option': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': True,
                'createOrder': True,
                'createPostOnlyOrder': True,
                'createReduceOnlyOrder': True,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchBorrowInterest': True,
                'fetchBorrowRate': True,
                'fetchBorrowRateHistories': True,
                'fetchBorrowRateHistory': True,
                'fetchBorrowRates': True,
                'fetchClosedOrders': None,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchFundingHistory': True,
                'fetchFundingRate': True,
                'fetchFundingRateHistory': True,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': True,
                'fetchLeverageTiers': False,
                'fetchMarginMode': False,
                'fetchMarketLeverageTiers': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterest': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchOrderTrades': True,
                'fetchPosition': False,
                'fetchPositionMode': False,
                'fetchPositions': True,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': False,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': True,
                'fetchTransactionFees': None,
                'fetchTransfer': None,
                'fetchTransfers': None,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'setLeverage': True,
                'setMarginMode': False,  # FTX only supports cross margin
                'setPositionMode': False,
                'transfer': True,
                'withdraw': True,
            },
            'timeframes': {
                '15s': '15',
                '1m': '60',
                '5m': '300',
                '15m': '900',
                '1h': '3600',
                '4h': '14400',
                '1d': '86400',
                '3d': '259200',
                '1w': '604800',
                '2w': '1209600',
                # the exchange does not align candles to the start of the month
                # it can only fetch candles in fixed intervals of multiples of whole days
                # that works for all timeframes, except the monthly timeframe
                # because months have varying numbers of days
                '1M': '2592000',
            },
            'api': {
                'public': {
                    'get': {
                        'coins': 1,
                        # markets
                        'markets': 1,
                        'markets/{market_name}': 1,
                        'markets/{market_name}/orderbook': 1,  # ?depth={depth}
                        'markets/{market_name}/trades': 1,  # ?limit={limit}&start_time={start_time}&end_time={end_time}
                        'markets/{market_name}/candles': 1,  # ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time}
                        # futures
                        'futures': 1,
                        'futures/{future_name}': 1,
                        'futures/{future_name}/stats': 1,
                        'funding_rates': 1,
                        'indexes/{index_name}/weights': 1,
                        'expired_futures': 1,
                        'indexes/{market_name}/candles': 1,  # ?resolution={resolution}&limit={limit}&start_time={start_time}&end_time={end_time}
                        # wallet
                        'wallet/coins': 1,
                        # leverage tokens
                        'lt/tokens': 1,
                        'lt/{token_name}': 1,
                        # etfs
                        'etfs/rebalance_info': 1,
                        # options
                        'options/requests': 1,
                        'options/trades': 1,
                        'options/historical_volumes/BTC': 1,
                        'stats/24h_options_volume': 1,
                        'options/open_interest/BTC': 1,
                        'options/historical_open_interest/BTC': 1,
                        # spot margin
                        'spot_margin/history': 1,
                        'spot_margin/borrow_summary': 1,
                        # nfts
                        'nft/nfts': 1,
                        'nft/{nft_id}': 1,
                        'nft/{nft_id}/trades': 1,
                        'nft/all_trades': 1,
                        'nft/{nft_id}/account_info': 1,
                        'nft/collections': 1,
                        # ftx pay
                        'ftxpay/apps/{user_specific_id}/details': 1,
                    },
                    'post': {
                        'ftxpay/apps/{user_specific_id}/orders': 1,
                    },
                },
                'private': {
                    'get': {
                        # subaccounts
                        'subaccounts': 1,
                        'subaccounts/{nickname}/balances': 1,
                        # account
                        'account': 1,
                        'positions': 1,
                        # wallet
                        'wallet/balances': 1,
                        'wallet/all_balances': 1,
                        'wallet/deposit_address/{coin}': 1,  # ?method={method}
                        'wallet/deposits': 1,
                        'wallet/withdrawals': 1,
                        'wallet/airdrops': 1,
                        'wallet/withdrawal_fee': 1,
                        'wallet/saved_addresses': 1,
                        # orders
                        'orders': 1,  # ?market={market}
                        'orders/history': 1,  # ?market={market}
                        'orders/{order_id}': 1,
                        'orders/by_client_id/{client_order_id}': 1,
                        # conditional orders
                        'conditional_orders': 1,  # ?market={market}
                        'conditional_orders/{conditional_order_id}/triggers': 1,
                        'conditional_orders/history': 1,  # ?market={market}
                        'fills': 1,  # ?market={market}
                        'funding_payments': 1,
                        # leverage tokens
                        'lt/balances': 1,
                        'lt/creations': 1,
                        'lt/redemptions': 1,
                        # options
                        'options/my_requests': 1,
                        'options/requests/{request_id}/quotes': 1,
                        'options/my_quotes': 1,
                        'options/account_info': 1,
                        'options/positions': 1,
                        'options/fills': 1,
                        # staking
                        'staking/stakes': 1,
                        'staking/unstake_requests': 1,
                        'staking/balances': 1,
                        'staking/staking_rewards': 1,
                        # otc
                        'otc/quotes/{quoteId}': 1,
                        # spot margin
                        'spot_margin/borrow_rates': 1,
                        'spot_margin/lending_rates': 1,
                        'spot_margin/market_info': 1,  # ?market={market}
                        'spot_margin/borrow_history': 1,
                        'spot_margin/lending_history': 1,
                        'spot_margin/offers': 1,
                        'spot_margin/lending_info': 1,
                        # nfts
                        'nft/balances': 1,
                        'nft/bids': 1,
                        'nft/deposits': 1,
                        'nft/withdrawals': 1,
                        'nft/fills': 1,
                        'nft/gallery/{gallery_id}': 1,
                        'nft/gallery_settings': 1,
                        # latency statistics
                        'stats/latency_stats': 1,
                        # pnl
                        'pnl/historical_changes': 1,
                        # support tickets
                        'support/tickets': 1,
                        'support/tickets/{ticketId}/messages': 1,
                        'support/tickets/count_unread': 1,
                        'twap_orders': 1,
                        'twap_orders/{twap_order_id}': 1,
                        'historical_balances/requests': 1,
                        'historical_balances/requests/{request_id}': 1,
                        'fast_access_settings/list_ips': 1,
                    },
                    'post': {
                        # subaccounts
                        'subaccounts': 1,
                        'subaccounts/update_name': 1,
                        'subaccounts/transfer': 1,
                        # account
                        'account/leverage': 1,
                        # wallet
                        'wallet/deposit_address/list': 1,
                        'wallet/withdrawals': 90,
                        'wallet/saved_addresses': 1,
                        # orders
                        'orders': 1,
                        'conditional_orders': 1,
                        'orders/{order_id}/modify': 1,
                        'orders/by_client_id/{client_order_id}/modify': 1,
                        'conditional_orders/{order_id}/modify': 1,
                        # leverage tokens
                        'lt/{token_name}/create': 1,
                        'lt/{token_name}/redeem': 1,
                        # options
                        'options/requests': 1,
                        'options/requests/{request_id}/quotes': 1,
                        'options/quotes/{quote_id}/accept': 1,
                        # staking
                        'staking/unstake_requests': 1,
                        'srm_stakes/stakes': 1,
                        # otc
                        'otc/quotes/{quote_id}/accept': 1,
                        'otc/quotes': 1,
                        # spot margin
                        'spot_margin/offers': 1,
                        # nfts
                        'nft/offer': 1,
                        'nft/buy': 1,
                        'nft/auction': 1,
                        'nft/edit_auction': 1,
                        'nft/cancel_auction': 1,
                        'nft/bids': 1,
                        'nft/redeem': 1,
                        'nft/gallery_settings': 1,
                        # ftx pay
                        'ftxpay/apps/{user_specific_id}/orders': 1,
                        # support tickets
                        'support/tickets': 1,
                        'support/tickets/{ticketId}/messages': 1,
                        'support/tickets/{ticketId}/status': 1,
                        'support/tickets/{ticketId}/mark_as_read': 1,
                        'twap_orders': 1,
                        'historical_balances/requests': 1,
                        'fast_access_settings/add_ip': 1,
                        'fast_access_settings/enable_ip/{ipAddress}': 1,
                        'fast_access_settings/disable_ip/{ipAddress}': 1,
                    },
                    'delete': {
                        # subaccounts
                        'subaccounts': 1,
                        # wallet
                        'wallet/saved_addresses/{saved_address_id}': 1,
                        # orders
                        'orders/{order_id}': 1,
                        'orders/by_client_id/{client_order_id}': 1,
                        'orders': 1,
                        'conditional_orders/{order_id}': 1,
                        'bulk_orders': 1,
                        'bulk_orders_by_client_id': 1,
                        # options
                        'options/requests/{request_id}': 1,
                        'options/quotes/{quote_id}': 1,
                        # staking
                        'staking/unstake_requests/{request_id}': 1,
                        'twap_orders/{twap_order_id}': 1,
                        'fast_access_settings/delete_ip/{ipAddress}': 1,
                    },
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'maker': self.parse_number('0.0002'),
                    'taker': self.parse_number('0.0007'),
                    'tiers': {
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.0007')],
                            [self.parse_number('2000000'), self.parse_number('0.0006')],
                            [self.parse_number('5000000'), self.parse_number('0.00055')],
                            [self.parse_number('10000000'), self.parse_number('0.0005')],
                            [self.parse_number('25000000'), self.parse_number('0.0045')],
                            [self.parse_number('50000000'), self.parse_number('0.0004')],
                        ],
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.0002')],
                            [self.parse_number('2000000'), self.parse_number('0.00015')],
                            [self.parse_number('5000000'), self.parse_number('0.0001')],
                            [self.parse_number('10000000'), self.parse_number('0.00005')],
                            [self.parse_number('25000000'), self.parse_number('0')],
                            [self.parse_number('50000000'), self.parse_number('0')],
                        ],
                    },
                },
                'funding': {
                    'withdraw': {},
                },
            },
            'exceptions': {
                'exact': {
                    'Slow down': RateLimitExceeded,  # {"error":"Slow down","success":false}
                    'Size too small for provide': InvalidOrder,  # {"error":"Size too small for provide","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}
                    'Invalid price': InvalidOrder,  # {"success":false,"error":"Invalid price"}
                    '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}
                    'Duplicate client order ID': DuplicateOrderId,  # {"error":"Duplicate client order ID","success":false}
                    'Spot orders cannot be reduce-only': InvalidOrder,  # {"error":"Spot orders cannot be reduce-only","success":false}
                    'Invalid reduce-only order': InvalidOrder,  # {"error":"Invalid reduce-only order","success":false}
                    'Account does not have enough balances': InsufficientFunds,  # {"success":false,"error":"Account does not have enough balances"}
                    'Not authorized for subaccount-specific access': PermissionDenied,  # {"success":false,"error":"Not authorized for subaccount-specific access"}
                    'Not approved to trade self product': PermissionDenied,  # {"success":false,"error":"Not approved to trade self product"}
                    'Internal Error': ExchangeNotAvailable,  # {"success":false,"error":"Internal Error"}
                    'Order took too long to process': ExchangeNotAvailable,  # {"success":false,"error":"Order took too long to process"}
                },
                'broad': {
                    # {"error":"Not logged in","success":false}
                    # {"error":"Not logged in: Invalid API key","success":false}
                    'Not logged in': AuthenticationError,
                    '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 subaccount': AuthenticationError,
                    'No such future': BadSymbol,
                    'No such market': BadSymbol,
                    'Do not send more than': RateLimitExceeded,
                    'Cannot send more than': RateLimitExceeded,  # {"success":false,"error":"Cannot send more than 1500 requests per minute"}
                    '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}
                    'Try again': ExchangeNotAvailable,  # {"error":"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
                'fetchMarkets': {
                    # the expiry datetime may be None for expiring futures, https://github.com/ccxt/ccxt/pull/12692
                    'throwOnUndefinedExpiry': False,
                },
                'cancelOrder': {
                    'method': 'privateDeleteOrdersOrderId',  # privateDeleteConditionalOrdersOrderId
                },
                'fetchOpenOrders': {
                    'method': 'privateGetOrders',  # privateGetConditionalOrders
                },
                'fetchOrders': {
                    'method': 'privateGetOrdersHistory',  # privateGetConditionalOrdersHistory
                },
                'sign': {
                    'ftx.com': 'FTX',
                    'ftx.us': 'FTXUS',
                },
                'networks': {
                    'AVAX': 'avax',
                    'BEP2': 'bep2',
                    'BEP20': 'bsc',
                    'BNB': 'bep2',
                    'BSC': 'bsc',
                    'ERC20': 'erc20',
                    'ETH': 'eth',
                    'FTM': 'ftm',
                    'MATIC': 'matic',
                    'OMNI': 'omni',
                    'SOL': 'sol',
                    'SPL': 'sol',
                    'TRC20': 'trx',
                    'TRX': 'trx',
                },
            },
            'commonCurrencies': {
                'AMC': 'AMC Entertainment Holdings',
                'STARS': 'StarLaunch',
            },
        })

    def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: an associative dictionary of currencies
        """
        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,
                'deposit': None,
                'withdraw': None,
                'fee': None,
                'precision': None,
                'limits': {
                    'withdraw': {'min': None, 'max': None},
                    'amount': {'min': None, 'max': None},
                },
            }
        return result

    def fetch_markets(self, params={}):
        """
        retrieves data on all markets for ftx
        :param dict params: extra parameters specific to the exchange api endpoint
        :returns [dict]: an array of objects representing market data
        """
        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
        #             },
        #         ],
        #     }
        #
        #     {
        #         name: "BTC-PERP",
        #         enabled:  True,
        #         postOnly:  False,
        #         priceIncrement: "1.0",
        #         sizeIncrement: "0.0001",
        #         minProvideSize: "0.001",
        #         last: "60397.0",
        #         bid: "60387.0",
        #         ask: "60388.0",
        #         price: "60388.0",
        #         type: "future",
        #         baseCurrency:  null,
        #         quoteCurrency:  null,
        #         underlying: "BTC",
        #         restricted:  False,
        #         highLeverageFeeExempt:  True,
        #         change1h: "-0.0036463231533270636",
        #         change24h: "-0.01844838515677064",
        #         changeBod: "-0.010130151132675475",
        #         quoteVolume24h: "2892083192.6099",
        #         volumeUsd24h: "2892083192.6099"
        #     }
        #
        allFuturesResponse = None
        if self.has['future'] and (self.hostname != 'ftx.us'):
            allFuturesResponse = self.publicGetFutures()
        #
        #    {
        #        success: True,
        #        result: [
        #            {
        #                name: "1INCH-PERP",
        #                underlying: "1INCH",
        #                description: "1INCH Token Perpetual Futures",
        #                type: "perpetual",
        #                expiry: null,
        #                perpetual: True,
        #                expired: False,
        #                enabled: True,
        #                postOnly: False,
        #                priceIncrement: "0.0001",
        #                sizeIncrement: "1.0",
        #                last: "2.5556",
        #                bid: "2.5555",
        #                ask: "2.5563",
        #                index: "2.5612449804010833",
        #                mark: "2.5587",
        #                imfFactor: "0.0005",
        #                lowerBound: "2.4315",
        #                upperBound: "2.6893",
        #                underlyingDescription: "1INCH Token",
        #                expiryDescription: "Perpetual",
        #                moveStart: null,
        #                marginPrice: "2.5587",
        #                positionLimitWeight: "20.0",
        #                group: "perpetual",
        #                change1h: "0.00799716356760164",
        #                change24h: "0.004909276569004792",
        #                changeBod: "0.008394419484511705",
        #                volumeUsd24h: "17834492.0818",
        #                volume: "7224898.0",
        #                openInterest: "5597917.0",
        #                openInterestUsd: "14323390.2279",
        #            },
        #            ...
        #        ],
        #    }
        #
        result = []
        markets = self.safe_value(response, 'result', [])
        allFutures = self.safe_value(allFuturesResponse, 'result', [])
        allFuturesDict = self.index_by(allFutures, 'name')
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'name')
            future = self.safe_value(allFuturesDict, id)
            marketType = self.safe_string(market, 'type')
            contract = (marketType == 'future')
            baseId = self.safe_string_2(market, 'baseCurrency', 'underlying')
            quoteId = self.safe_string(market, 'quoteCurrency', 'USD')
            settleId = 'USD' if contract else None
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            settle = self.safe_currency_code(settleId)
            spot = not contract
            margin = not contract
            perpetual = self.safe_value(future, 'perpetual', False)
            swap = perpetual
            option = False
            isFuture = contract and not swap
            expiry = None
            expiryDatetime = self.safe_string(future, 'expiry')
            type = 'spot'
            symbol = base + '/' + quote
            if swap:
                type = 'swap'
                symbol = base + '/' + quote + ':' + settle
            elif isFuture:
                type = 'future'
                expiry = self.parse8601(expiryDatetime)
                if expiry is None:
                    # it is likely a future that is expiring in self moment
                    options = self.safe_value(self.options, 'fetchMarkets', {})
                    throwOnUndefinedExpiry = self.safe_value(options, 'throwOnUndefinedExpiry', False)
                    if throwOnUndefinedExpiry:
                        raise BadResponse(self.id + " symbol '" + id + "' is a future contract with an invalid expiry datetime.")
                    else:
                        continue
                parsedId = id.split('-')
                length = len(parsedId)
                if length > 2:
                    # handling for MOVE contracts
                    # BTC-MOVE-2022Q1
                    # BTC-MOVE-0106
                    # BTC-MOVE-WK-0121
                    parsedId.pop()
                    # remove expiry
                    # ['BTC', 'MOVE']
                    # ['BTC', 'MOVE']
                    # ['BTC', 'MOVE', 'WK']
                    base = '-'.join(parsedId)
                symbol = base + '/' + quote + ':' + settle + '-' + self.yymmdd(expiry, '')
            # check if a market is a spot or future market
            sizeIncrement = self.safe_string(market, 'sizeIncrement')
            minProvideSize = self.safe_string(market, 'minProvideSize')
            minAmountString = sizeIncrement
            if minProvideSize is not None:
                minAmountString = sizeIncrement if Precise.string_gt(minProvideSize, sizeIncrement) else minProvideSize
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': settle,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': settleId,
                'type': type,
                'spot': spot,
                'margin': margin,
                'swap': swap,
                'future': isFuture,
                'option': option,
                'active': self.safe_value(market, 'enabled'),
                'contract': contract,
                'linear': True if contract else None,
                'inverse': False if contract else None,
                'contractSize': self.parse_number('1'),
                'expiry': expiry,
                'expiryDatetime': self.iso8601(expiry),
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(sizeIncrement),
                    'price': self.safe_number(market, 'priceIncrement'),
                },
                'limits': {
                    'leverage': {
                        'min': self.parse_number('1'),
                        'max': self.parse_number('20'),
                    },
                    'amount': {
                        'min': self.parse_number(minAmountString),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        '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,
        #     }
        #
        marketId = self.safe_string(ticker, 'name')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        last = self.safe_string(ticker, 'last')
        timestamp = self.safe_timestamp(ticker, 'time')
        percentage = self.safe_string(ticker, 'change24h')
        if percentage is not None:
            percentage = Precise.string_mul(percentage, '100')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': self.safe_string(ticker, 'bidSize'),
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': self.safe_string(ticker, 'askSize'),
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': None,
            'quoteVolume': self.safe_string(ticker, 'quoteVolume24h'),
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol, params={}):
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        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 fetch_tickers(self, symbols=None, params={}):
        """
        fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
        :param [str]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: an array of `ticker structures <https://docs.ccxt.com/en/latest/manual.html#ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        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={}):
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int|None limit: the maximum amount of order book entries to return
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/en/latest/manual.html#order-book-structure>` indexed by market symbols
        """
        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, 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]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int|None since: timestamp in ms of the earliest candle to fetch
        :param int|None limit: the maximum amount of candles to fetch
        :param dict params: extra parameters specific to the ftx api endpoint
        :param str|None params['price']: "index" for index price candles
        :param int|None params['until']: timestamp in ms of the latest candle to fetch
        :returns [[int]]: A list of candles ordered as timestamp, open, high, low, close, volume(units in quote currency)
        """
        self.load_markets()
        market, marketId = self.get_market_params(symbol, 'market_name', params)
        # max 1501 candles, including the current candle when since is not specified
        maxLimit = 5000
        defaultLimit = 1500
        limit = defaultLimit if (limit is None) else min(limit, maxLimit)
        request = {
            'resolution': self.timeframes[timeframe],
            'market_name': marketId,
            # 'start_time': int(since / 1000),
            # 'end_time': self.seconds(),
            'limit': limit,
        }
        price = self.safe_string(params, 'price')
        until = self.safe_integer(params, 'until')
        params = self.omit(params, ['price', 'until'])
        duration = self.parse_timeframe(timeframe)
        now = self.seconds()
        if since is None:  # Issue  #12855, inconsistent results if since is not provided
            request['end_time'] = now
            request['start_time'] = self.sum(now, -limit * duration)
        else:
            startTime = int(since / 1000)
            request['start_time'] = startTime
            endTime = self.sum(startTime, limit * duration)
            request['end_time'] = min(endTime, now)
        if duration > 86400:
            wholeDaysInTimeframe = int(duration / 86400)
            request['limit'] = min(limit * wholeDaysInTimeframe, maxLimit)
        if until is not None:
            request['end_time'] = int(until / 1000)
        method = 'publicGetMarketsMarketNameCandles'
        if price == 'index':
            if symbol in self.markets:
                request['market_name'] = market['baseId']
            method = 'publicGetIndexesMarketNameCandles'
        response = getattr(self, method)(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')
        # a workaround for the OTC trades, they don't have a symbol
        baseId = self.safe_string(trade, 'baseCurrency')
        quoteId = self.safe_string(trade, 'quoteCurrency')
        defaultMarketId = None
        if (baseId is not None) and (quoteId is not None):
            defaultMarketId = baseId + '/' + quoteId
        marketId = self.safe_string(trade, 'market', defaultMarketId)
        market = self.safe_market(marketId, market, '/')
        symbol = market['symbol']
        timestamp = self.parse8601(self.safe_string(trade, 'time'))
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'size')
        side = self.safe_string(trade, 'side')
        fee = None
        feeCostString = self.safe_string(trade, 'fee')
        if feeCostString is not None:
            feeCurrencyId = self.safe_string(trade, 'feeCurrency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
                'rate': self.safe_string(trade, 'feeRate'),
            }
        orderId = self.safe_string(trade, 'orderId')
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': fee,
        }, market)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        """
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int|None since: timestamp in ms of the earliest trade to fetch
        :param int|None limit: the maximum amount of trades to fetch
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html?#public-trades>`
        """
        self.load_markets()
        market, marketId = self.get_market_params(symbol, 'market_name', params)
        request = {
            'market_name': marketId,
        }
        if since is not None:
            # the exchange aligns results to end_time returning 5000 trades max
            # the user must set the end_time(in seconds) close enough to start_time
            # for a proper pagination, fetch the most recent trades first
            # then set the end_time parameter to the timestamp of the last trade
            # start_time and end_time must be in seconds, divided by a thousand
            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 parse_trading_fee(self, fee, market=None):
        #
        #     {
        #         "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,
        #             },
        #         ],
        #     },
        #
        symbol = self.safe_symbol(None, market)
        maker = self.safe_number(fee, 'makerFee')
        taker = self.safe_number(fee, 'takerFee')
        return {
            'info': fee,
            'symbol': symbol,
            'maker': maker,
            'taker': taker,
            'percentage': True,
            'tierBased': True,
        }

    def parse_trading_fees(self, response):
        result = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            market = self.market(symbol)
            result[symbol] = self.parse_trading_fee(response, market)
        return result

    def fetch_trading_fee(self, symbol, params={}):
        """
        fetch the trading fee for a market
        :param str symbol: unified symbol of the market to fetch the fee for
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a `fee structure <https://docs.ccxt.com/en/latest/manual.html#fee-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        response = self.privateGetAccount(params)
        result = self.safe_value(response, 'result', {})
        return self.parse_trading_fee(result, market)

    def fetch_trading_fees(self, params={}):
        """
        fetch the trading fees for multiple markets
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/en/latest/manual.html#fee-structure>` indexed by market symbols
        """
        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 self.parse_trading_fees(result)

    def fetch_funding_rate_history(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches historical funding rate prices
        :param str|None symbol: unified symbol of the market to fetch the funding rate history for
        :param int|None since: timestamp in ms of the earliest funding rate to fetch
        :param int|None limit: the maximum amount of `funding rate structures <https://docs.ccxt.com/en/latest/manual.html?#funding-rate-history-structure>` to fetch
        :param dict params: extra parameters specific to the ftx api endpoint
        :param int|None params['until']: timestamp in ms of the latest funding rate to fetch
        :returns [dict]: a list of `funding rate structures <https://docs.ccxt.com/en/latest/manual.html?#funding-rate-history-structure>`
        """
        self.load_markets()
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            request['future'] = market['id']
        if since is not None:
            request['start_time'] = int(since / 1000)
        until = self.safe_integer_2(params, 'until', 'till')  # unified in milliseconds
        params = self.omit(params, ['until', 'till'])
        if until is not None:
            request['end_time'] = int(until / 1000)
        response = self.publicGetFundingRates(self.extend(request, params))
        #
        #     {
        #        "success": True,
        #        "result": [
        #          {
        #            "future": "BTC-PERP",
        #            "rate": 0.0025,
        #            "time": "2019-06-02T08:00:00+00:00"
        #          }
        #        ]
        #      }
        #
        result = self.safe_value(response, 'result', [])
        rates = []
        for i in range(0, len(result)):
            entry = result[i]
            marketId = self.safe_string(entry, 'future')
            timestamp = self.parse8601(self.safe_string(entry, 'time'))
            rates.append({
                'info': entry,
                'symbol': self.safe_symbol(marketId),
                'fundingRate': self.safe_number(entry, 'rate'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
            })
        sorted = self.sort_by(rates, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)

    def parse_balance(self, response):
        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_2(balance, 'availableWithoutBorrow', 'free')
            account['total'] = self.safe_string(balance, 'total')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}):
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/en/latest/manual.html?#balance-structure>`
        """
        self.load_markets()
        response = self.privateGetWalletBalances(params)
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "coin": "USDTBEAR",
        #                 "free": 2320.2,
        #                 "total": 2340.2
        #             },
        #         ],
        #     }
        #
        return self.parse_balance(response)

    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_string(order, 'size')
        filled = self.safe_string(order, 'filledSize')
        remaining = self.safe_string(order, 'remainingSize')
        if Precise.string_equals(remaining, '0'):
            remaining = Precise.string_sub(amount, filled)
            if Precise.string_gt(remaining, '0'):
                status = 'canceled'
        marketId = self.safe_string(order, 'market')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        if symbol is None:
            # support for delisted market ids
            # https://github.com/ccxt/ccxt/issues/7113
            symbol = marketId
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'type')
        average = self.safe_string(order, 'avgFillPrice')
        price = self.safe_string_2(order, 'price', 'triggerPrice', average)
        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')
        ioc = self.safe_value(order, 'ioc')
        timeInForce = None
        if ioc:
            timeInForce = 'IOC'
        if postOnly:
            timeInForce = 'PO'
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'reduceOnly': self.safe_value(order, 'reduceOnly'),
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'amount': amount,
            'cost': None,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
        }, market)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        """
        create a trade order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float|None price: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
            'side': side,  # 'buy' or 'sell'
            # 'price': 0.306525,  # send null for market orders
            '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
            # 'triggerPrice': 0.306525,  # required for stop and takeProfit orders
            # 'trailValue': -0.306525,  # required for trailingStop orders, negative for "sell"; positive for "buy"
            # 'orderPrice': 0.306525,  # optional, for stop and takeProfit orders only(market by default). If not specified, a market order will be submitted
        }
        reduceOnly = self.safe_value(params, 'reduceOnly')
        if reduceOnly is True:
            request['reduceOnly'] = reduceOnly
        clientOrderId = self.safe_string_2(params, 'clientId', 'clientOrderId')
        if clientOrderId is not None:
            request['clientId'] = clientOrderId
            params = self.omit(params, ['clientId', 'clientOrderId'])
        method = None
        triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
        stopLossPrice = self.safe_value(params, 'stopLossPrice')
        takeProfitPrice = self.safe_value(params, 'takeProfitPrice')
        isTakeProfit = type == 'takeProfit'
        isStopLoss = type == 'stop'
        isTriggerPrice = False
        if triggerPrice is not None:
            isTriggerPrice = not isTakeProfit and not isStopLoss
        elif takeProfitPrice is not None:
            isTakeProfit = True
            triggerPrice = takeProfitPrice
        elif stopLossPrice is not None:
            isStopLoss = True
            triggerPrice = stopLossPrice
        if not isTriggerPrice:
            request['type'] = type
        isStopOrder = isTakeProfit or isStopLoss or isTriggerPrice
        params = self.omit(params, ['stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice'])
        if isStopOrder:
            if triggerPrice is None:
                if isTakeProfit:
                    raise ArgumentsRequired(self.id + ' createOrder() requires a triggerPrice param, a stopPrice or a takeProfitPrice param for a takeProfit order')
                elif isStopLoss:
                    raise ArgumentsRequired(self.id + ' createOrder() requires a triggerPrice param, a stopPrice or a stopLossPrice param for a stop order')
            method = 'privatePostConditionalOrders'
            request['triggerPrice'] = float(self.price_to_precision(symbol, triggerPrice))
            if (type == 'limit') and (price is None):
                raise ArgumentsRequired(self.id + ' createOrder() requires a price argument for stop limit orders')
            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
            if isStopLoss or isTakeProfit:
                request['type'] = 'stop' if isStopLoss else 'takeProfit'
        elif (type == 'limit') or (type == 'market'):
            method = 'privatePostOrders'
            isMarketOrder = False
            if type == 'limit':
                request['price'] = float(self.price_to_precision(symbol, price))
            elif type == 'market':
                request['price'] = None
                isMarketOrder = True
            timeInForce = self.safe_string(params, 'timeInForce')
            postOnly = self.is_post_only(isMarketOrder, None, params)
            params = self.omit(params, ['timeInForce', 'postOnly'])
            if timeInForce is not None:
                if not ((timeInForce == 'IOC') or (timeInForce == 'PO')):
                    raise InvalidOrder(self.id + ' createOrder() does not accept timeInForce: ' + timeInForce + ' orders, only IOC and PO orders are allowed')
            ioc = (timeInForce == 'IOC')
            if postOnly:
                request['postOnly'] = True
            if ioc:
                request['ioc'] = True
        elif type == 'trailingStop':
            trailValue = self.safe_number(params, 'trailValue', price)
            if trailValue is None:
                raise ArgumentsRequired(self.id + ' createOrder() requires a trailValue parameter or a price argument(negative or positive) for a ' + type + ' order')
            method = 'privatePostConditionalOrders'
            request['trailValue'] = float(self.price_to_precision(symbol, trailValue))  # 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))
        #
        # regular 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":{
        #             "id":215826320,
        #             "market":"BTC/USD",
        #             "future":null,
        #             "side":"sell",
        #             "type":"take_profit",  # the API accepts the "takeProfit" string in camelCase notation but returns the "take_profit" type with underscore
        #             "orderPrice":40000.0,
        #             "triggerPrice":39000.0,
        #             "size":0.001,
        #             "status":"open",
        #             "createdAt":"2022-06-12T15:41:41.836788+00:00",
        #             "triggeredAt":null,
        #             "orderId":null,
        #             "error":null,
        #             "reduceOnly":false,
        #             "trailValue":null,
        #             "trailStart":null,
        #             "cancelledAt":null,
        #             "cancelReason":null,
        #             "retryUntilFilled":false,
        #             "orderType":"limit"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_order(result, market)

    def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {}
        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 = 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)

    def cancel_order(self, id, symbol=None, params={}):
        """
        cancels an open order
        :param str id: order id
        :param str|None symbol: not used by ftx cancelOrder()
        :param dict params: extra parameters specific to the ftx api endpoint
        :param bool params['stop']: True if cancelling a stop/trigger order
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        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')  # Deprecated: use params.stop instead
        isTriggerOrder = None
        isTriggerOrder, params = self.is_trigger_order(params)
        clientOrderId = self.safe_value_2(params, 'client_order_id', 'clientOrderId')
        if clientOrderId is None:
            request['order_id'] = int(id)
            if isTriggerOrder or self.in_array(type, ['stop', 'trailingStop', 'takeProfit']):
                method = 'privateDeleteConditionalOrdersOrderId'
        else:
            request['client_order_id'] = clientOrderId
            method = 'privateDeleteOrdersByClientIdClientOrderId'
        query = self.omit(params, ['method', 'type', 'client_order_id', 'clientOrderId'])
        response = getattr(self, method)(self.extend(request, query))
        #
        #     {
        #         "success": True,
        #         "result": "Order queued for cancelation"
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return result

    def cancel_orders(self, ids, symbol=None, params={}):
        """
        cancel multiple orders
        :param [str] ids: order ids
        :param str|None symbol: not used by ftx cancelOrders()
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: raw - a list of order ids queued for cancelation
        """
        self.load_markets()
        # https://docs.ccxt.com/en/latest/manual.html#user-defined-clientorderid
        clientOrderIds = self.safe_value(params, 'clientOrderIds')
        # FTX doesn't have endpoint for trigger orders related to cancelOrders
        if clientOrderIds is not None:
            #
            #     {success: True, result: ['billy', 'bob', 'gina']}
            #
            return self.privateDeleteBulkOrdersByClientId(params)
        else:
            request = {
                'orderIds': ids,
            }
            #
            #     {success: True, result: [181542119006, 181542179014]}
            #
            return self.privateDeleteBulkOrders(self.extend(request, params))

    def cancel_all_orders(self, symbol=None, params={}):
        """
        cancel all open orders
        :param str|None symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        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
        isTriggerOrder = None
        isTriggerOrder, params = self.is_trigger_order(params)
        # if user wants to cancel only conditional orders, then set below fields. Otherwise, request will cancel all orders - conditional and regular orders
        if isTriggerOrder:
            request['conditionalOrdersOnly'] = True
            request['limitOrdersOnly'] = False
        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={}):
        """
        fetches information on an order made by the user
        :param str|None symbol: not used by ftx fetchOrder
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        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 = 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)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch all unfilled currently open orders
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch open orders for
        :param int|None limit: the maximum number of  open orders structures to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        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')
        isTriggerOrder = None
        isTriggerOrder, params = self.is_trigger_order(params)
        if isTriggerOrder or self.in_array(type, ['trigger', 'stop', 'trailingStop', 'takeProfit']):
            method = 'privateGetConditionalOrders'
        query = self.omit(params, ['method', 'type'])
        response = 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)

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        """
        fetches information on multiple orders made by the user
        :param str|None symbol: unified market symbol of the market orders were made in
        :param int|None since: the earliest time in ms to fetch orders for
        :param int|None limit: the maximum number of  orde structures to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `order structures <https://docs.ccxt.com/en/latest/manual.html#order-structure>`
        """
        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')
        isTriggerOrder = None
        isTriggerOrder, params = self.is_trigger_order(params)
        if isTriggerOrder or self.in_array(type, ['trigger', 'stop', 'trailingStop', 'takeProfit']):
            method = 'privateGetConditionalOrdersHistory'
        query = self.omit(params, ['method', 'type'])
        response = 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)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        """
        fetch all the trades made from a single order
        :param str id: order id
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        request = {
            'orderId': id,
        }
        # if it's conditional order
        type = self.safe_value(params, 'type')
        params = self.omit(params, 'type')
        isTriggerOrder = None
        isTriggerOrder, params = self.is_trigger_order(params)
        if isTriggerOrder or self.in_array(type, ['trigger', 'stop', 'trailingStop', 'takeProfit']):
            # if it is conditional order, then it would have a reference order id to be sent to the specific endpoint, from where we will get the actual order ID and then use that ID
            realOrderId = self.fetch_order_if_from_conditional_order(id)
            if realOrderId is not None:
                request['orderId'] = realOrderId
            else:
                # if order-id was not found, then there were no fills and no need to make any further request
                return []
        return self.fetch_my_trades(symbol, since, limit, self.extend(request, params))

    def fetch_order_if_from_conditional_order(self, id):
        request = {
            'conditional_order_id': id,
        }
        response = self.privateGetConditionalOrdersConditionalOrderIdTriggers(request)
        # Note: if endpoint retuns non-empy "result" property, then it means order had fill and returns the order reference ID, which is the real id of the regular order
        #
        # {
        #     "success": True,
        #     "result": [
        #         {
        #             "time": "2022-03-29T04:54:04.390665+00:00",
        #             "orderId": 132144259673,  # self is not the same conditional-order's id, instead it is the regular order id, which can be used in regular methods
        #             "error": null,
        #             "orderSize": 0.1234,
        #             "filledSize": 0.1234
        #         }
        #     ]
        # }
        #
        # Also note, the above endpoint seems to return one object in array
        result = self.safe_value(response, 'result')
        orderObject = self.safe_value(result, 0, {})
        return self.safe_string(orderObject, 'orderId')

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch trades specific to you account
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch trades for
        :param int|None limit: the maximum number of trades structures to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :param int|None params['until']: timestamp in ms of the latest trade
        :returns [dict]: a list of `trade structures <https://docs.ccxt.com/en/latest/manual.html#trade-structure>`
        """
        self.load_markets()
        market, marketId = self.get_market_params(symbol, 'market', params)
        request = {}
        if marketId is not None:
            request['market'] = marketId
        if market is not None:
            symbol = market['symbol']
        until = self.safe_integer_2(params, 'until', 'till')
        if since is not None:
            request['start_time'] = int(since / 1000)
            request['end_time'] = self.seconds()
        if until is not None:
            request['end_time'] = int(until / 1000)
            params = self.omit(params, ['until', 'till'])
        if limit is not None:
            request['limit'] = limit
        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 transfer(self, code, amount, fromAccount, toAccount, params={}):
        """
        transfer currency internally between wallets on the same account
        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a `transfer structure <https://docs.ccxt.com/en/latest/manual.html#transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
            'source': fromAccount,
            'destination': toAccount,
            'size': amount,
        }
        response = self.privatePostSubaccountsTransfer(self.extend(request, params))
        #
        #     {
        #         success: True,
        #         result: {
        #             id: '31222278',
        #             coin: 'USDT',
        #             size: '1.0',
        #             time: '2022-04-01T11:18:27.194188+00:00',
        #             notes: 'Transfer from main account to testSubaccount',
        #             status: 'complete'
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_transfer(result, currency)

    def parse_transfer(self, transfer, currency=None):
        #
        #     {
        #         id: '31222278',
        #         coin: 'USDT',
        #         size: '1.0',
        #         time: '2022-04-01T11:18:27.194188+00:00',
        #         notes: 'Transfer from main account to testSubaccount',
        #         status: 'complete'
        #     }
        #
        currencyId = self.safe_string(transfer, 'coin')
        notes = self.safe_string(transfer, 'notes', '')
        status = self.safe_string(transfer, 'status')
        fromTo = notes.replace('Transfer from ', '')
        parts = fromTo.split(' to ')
        fromAccount = self.safe_string(parts, 0)
        fromAccount = fromAccount.replace(' account', '')
        toAccount = self.safe_string(parts, 1)
        toAccount = toAccount.replace(' account', '')
        return {
            'info': transfer,
            'id': self.safe_string(transfer, 'id'),
            'timestamp': None,
            'datetime': self.safe_string(transfer, 'time'),
            'currency': self.safe_currency_code(currencyId, currency),
            'amount': self.safe_number(transfer, 'size'),
            'fromAccount': fromAccount,
            'toAccount': toAccount,
            'status': self.parse_transfer_status(status),
        }

    def parse_transfer_status(self, status):
        statuses = {
            'complete': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def withdraw(self, code, amount, address, tag=None, params={}):
        """
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str|None tag:
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a `transaction structure <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, 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 self.password is not None:
            request['password'] = self.password
        if tag is not None:
            request['tag'] = tag
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string_lower(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['method'] = network
            params = self.omit(params, 'network')
        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_positions(self, symbols=None, params={}):
        """
        fetch all open positions
        :param [str]|None symbols: list of unified market symbols
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `position structure <https://docs.ccxt.com/en/latest/manual.html#position-structure>`
        """
        self.load_markets()
        request = {
            'showAvgPrice': True,
        }
        response = self.privateGetPositions(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": [
        #             {
        #                 "cost": -31.7906,
        #                 "entryPrice": 138.22,
        #                 "estimatedLiquidationPrice": 152.1,
        #                 "future": "ETH-PERP",
        #                 "initialMarginRequirement": 0.1,
        #                 "longOrderSize": 1744.55,
        #                 "maintenanceMarginRequirement": 0.04,
        #                 "netSize": -0.23,
        #                 "openSize": 1744.32,
        #                 "realizedPnl": 3.39441714,
        #                 "shortOrderSize": 1732.09,
        #                 "recentAverageOpenPrice": 278.98,
        #                 "recentPnl": 2.44,
        #                 "recentBreakEvenPrice": 278.98,
        #                 "side": "sell",
        #                 "size": 0.23,
        #                 "unrealizedPnl": 0,
        #                 "collateralUsed": 3.17906
        #             }
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_positions(result, symbols)

    def parse_position(self, position, market=None):
        #
        #   {
        #     "future": "XMR-PERP",
        #     "size": "0.0",
        #     "side": "buy",
        #     "netSize": "0.0",
        #     "longOrderSize": "0.0",
        #     "shortOrderSize": "0.0",
        #     "cost": "0.0",
        #     "entryPrice": null,
        #     "unrealizedPnl": "0.0",
        #     "realizedPnl": "0.0",
        #     "initialMarginRequirement": "0.02",
        #     "maintenanceMarginRequirement": "0.006",
        #     "openSize": "0.0",
        #     "collateralUsed": "0.0",
        #     "estimatedLiquidationPrice": null
        #   }
        #
        contractsString = self.safe_string(position, 'size')
        rawSide = self.safe_string(position, 'side')
        side = 'long' if (rawSide == 'buy') else 'short'
        marketId = self.safe_string(position, 'future')
        symbol = self.safe_symbol(marketId, market)
        liquidationPriceString = self.safe_string(position, 'estimatedLiquidationPrice')
        initialMarginPercentage = self.safe_string(position, 'initialMarginRequirement')
        # on ftx the entryPrice is actually the mark price
        markPriceString = self.safe_string(position, 'entryPrice')
        notionalString = Precise.string_mul(contractsString, markPriceString)
        initialMargin = self.safe_string(position, 'collateralUsed')
        maintenanceMarginPercentageString = self.safe_string(position, 'maintenanceMarginRequirement')
        maintenanceMarginString = Precise.string_mul(notionalString, maintenanceMarginPercentageString)
        unrealizedPnlString = self.safe_string(position, 'recentPnl')
        percentage = self.parse_number(Precise.string_mul(Precise.string_div(unrealizedPnlString, initialMargin, 4), '100'))
        entryPriceString = self.safe_string(position, 'recentAverageOpenPrice')
        difference = None
        collateral = None
        marginRatio = None
        leverage = None
        if Precise.string_eq(liquidationPriceString, '0'):
            # position is fully collateralized
            collateral = notionalString
        elif entryPriceString is not None:
            # collateral = maintenanceMargin ±((markPrice - liquidationPrice) * size)
            if side == 'long':
                difference = Precise.string_sub(markPriceString, liquidationPriceString)
            else:
                difference = Precise.string_sub(liquidationPriceString, markPriceString)
            loss = Precise.string_mul(difference, contractsString)
            collateral = Precise.string_add(loss, maintenanceMarginString)
            leverage = self.parse_number(Precise.string_div(Precise.string_add(Precise.string_div(notionalString, collateral), '0.005'), '1', 2))
            marginRatio = self.parse_number(Precise.string_div(maintenanceMarginString, collateral, 4))
        # ftx has a weird definition of realizedPnl
        # it keeps the historical record of the realizedPnl per contract forever
        # so we cannot use self data
        return {
            'id': None,
            'info': position,
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'initialMargin': self.parse_number(initialMargin),
            'initialMarginPercentage': self.parse_number(initialMarginPercentage),
            'maintenanceMargin': self.parse_number(maintenanceMarginString),
            'maintenanceMarginPercentage': self.parse_number(maintenanceMarginPercentageString),
            'entryPrice': self.parse_number(entryPriceString),
            'notional': self.parse_number(notionalString),
            'leverage': leverage,
            'unrealizedPnl': self.parse_number(unrealizedPnlString),
            'contracts': self.parse_number(contractsString),
            'contractSize': self.safe_value(market, 'contractSize'),
            'marginRatio': marginRatio,
            'liquidationPrice': self.parse_number(liquidationPriceString),
            'markPrice': self.parse_number(markPriceString),
            'collateral': self.parse_number(collateral),
            'marginMode': 'cross',
            'side': side,
            'percentage': percentage,
        }

    def fetch_deposit_address(self, code, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/en/latest/manual.html#address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request = {
            'coin': currency['id'],
        }
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string_lower(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['method'] = network
            params = self.omit(params, 'network')
        response = self.privateGetWalletDepositAddressCoin(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
        #             "tag": null,
        #             "method": "erc20",
        #             "coin": null
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        networkId = self.safe_string(result, 'method')
        address = self.safe_string(result, 'address')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': self.safe_string(result, 'tag'),
            'network': self.safe_network(networkId),
            'info': response,
        }

    def safe_network(self, networkId):
        networksById = {
            'trx': 'TRC20',
            'erc20': 'ERC20',
            'sol': 'SOL',
            'bsc': 'BEP20',
            'bep2': 'BEP2',
        }
        return self.safe_string(networksById, networkId, networkId)

    def parse_transaction_status(self, status):
        statuses = {
            # what are other statuses here?
            'confirmed': 'ok',  # deposits
            'complete': 'ok',  # withdrawals
            'cancelled': 'canceled',  # deposits
        }
        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, str):
            tag = self.safe_string(address, 'tag')
            address = self.safe_string(address, 'address')
        else:
            tag = self.safe_string(transaction, 'tag')
        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),
            'network': None,
            '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,
            },
        }

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        """
        fetch all deposits made to an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch deposits for
        :param int|None limit: the maximum number of deposits structures to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        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', [])
        currency = None
        if code is not None:
            currency = self.currency(code)
        return self.parse_transactions(result, currency, since, limit, {'type': 'deposit'})

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        """
        fetch all withdrawals made from an account
        :param str|None code: unified currency code
        :param int|None since: the earliest time in ms to fetch withdrawals for
        :param int|None limit: the maximum number of withdrawals structures to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `transaction structures <https://docs.ccxt.com/en/latest/manual.html#transaction-structure>`
        """
        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', [])
        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)
        signOptions = self.safe_value(self.options, 'sign', {})
        headerPrefix = self.safe_string(signOptions, self.hostname, 'FTX')
        subaccountField = headerPrefix + '-SUBACCOUNT'
        chosenSubaccount = self.safe_string_2(params, subaccountField, 'subaccount')
        if chosenSubaccount is not None:
            params = self.omit(params, [subaccountField, 'subaccount'])
        query = self.omit(params, self.extract_params(path))
        baseUrl = self.implode_hostname(self.urls['api'][api])
        url = baseUrl + request
        if method == 'GET':
            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)
            headers[headerPrefix + '-KEY'] = self.apiKey
            headers[headerPrefix + '-TS'] = timestamp
            headers[headerPrefix + '-SIGN'] = signature
            if chosenSubaccount is not None:
                headers[subaccountField] = chosenSubaccount
        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

    def set_leverage(self, leverage, symbol=None, params={}):
        """
        set the level of leverage for a market
        :param float leverage: the rate of leverage
        :param str|None symbol: not used by ftx setLeverage()
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: response from the exchange
        """
        # WARNING: THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
        # AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
        if (leverage < 1) or (leverage > 20):
            raise BadRequest(self.id + ' setLeverage() leverage should be between 1 and 20')
        request = {
            'leverage': leverage,
        }
        return self.privatePostAccountLeverage(self.extend(request, params))

    def parse_income(self, income, market=None):
        #
        #   {
        #       "future": "ETH-PERP",
        #        "id": 33830,
        #        "payment": 0.0441342,
        #        "time": "2019-05-15T18:00:00+00:00",
        #        "rate": 0.0001
        #   }
        #
        marketId = self.safe_string(income, 'future')
        symbol = self.safe_symbol(marketId, market)
        amount = self.safe_number(income, 'payment')
        code = self.safe_currency_code('USD')
        id = self.safe_string(income, 'id')
        time = self.safe_string(income, 'time')
        timestamp = self.parse8601(time)
        rate = self.safe_number(income, 'rate')
        return {
            'info': income,
            'symbol': symbol,
            'code': code,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'id': id,
            'amount': amount,
            'rate': rate,
        }

    def parse_incomes(self, incomes, market=None, since=None, limit=None):
        result = []
        for i in range(0, len(incomes)):
            entry = incomes[i]
            parsed = self.parse_income(entry, market)
            result.append(parsed)
        sorted = self.sort_by(result, 'timestamp')
        return self.filter_by_since_limit(sorted, since, limit, 'timestamp')

    def fetch_funding_history(self, symbol=None, since=None, limit=None, params={}):
        """
        fetch the history of funding payments paid and received on self account
        :param str|None symbol: unified market symbol
        :param int|None since: the earliest time in ms to fetch funding history for
        :param int|None limit: the maximum number of funding history structures to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a `funding history structure <https://docs.ccxt.com/en/latest/manual.html#funding-history-structure>`
        """
        self.load_markets()
        request = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['future'] = market['id']
        if since is not None:
            request['start_time'] = int(since / 1000)
            request['end_time'] = self.seconds()
        till = self.safe_integer(params, 'till')
        if till is not None:
            request['end_time'] = int(till / 1000)
            params = self.omit(params, 'till')
        response = self.privateGetFundingPayments(self.extend(request, params))
        result = self.safe_value(response, 'result', [])
        return self.parse_incomes(result, market, since, limit)

    def parse_funding_rate(self, contract, market=None):
        #
        # perp
        #     {
        #       "volume": "71294.7636",
        #       "nextFundingRate": "0.000033",
        #       "nextFundingTime": "2021-10-14T20:00:00+00:00",
        #       "openInterest": "47142.994"
        #     }
        #
        # delivery
        #     {
        #       "volume": "4998.727",
        #       "predictedExpirationPrice": "3798.820141757",
        #       "openInterest": "48307.96"
        #     }
        #
        fundingRateDatetimeRaw = self.safe_string(contract, 'nextFundingTime')
        fundingRateTimestamp = self.parse8601(fundingRateDatetimeRaw)
        estimatedSettlePrice = self.safe_number(contract, 'predictedExpirationPrice')
        return {
            'info': contract,
            'symbol': market['symbol'],
            'markPrice': None,
            'indexPrice': None,
            'interestRate': self.parse_number('0'),
            'estimatedSettlePrice': estimatedSettlePrice,
            'timestamp': None,
            'datetime': None,
            'fundingRate': self.safe_number(contract, 'nextFundingRate'),
            'fundingTimestamp': fundingRateTimestamp,
            'fundingDatetime': self.iso8601(fundingRateTimestamp),
            'nextFundingRate': None,
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
        }

    def fetch_funding_rate(self, symbol, params={}):
        """
        fetch the current funding rate
        :param str symbol: unified market symbol
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a `funding rate structure <https://docs.ccxt.com/en/latest/manual.html#funding-rate-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'future_name': market['id'],
        }
        response = self.publicGetFuturesFutureNameStats(self.extend(request, params))
        #
        #     {
        #       "success": True,
        #       "result": {
        #         "volume": "71294.7636",
        #         "nextFundingRate": "0.000033",
        #         "nextFundingTime": "2021-10-14T20:00:00+00:00",
        #         "openInterest": "47142.994"
        #       }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_funding_rate(result, market)

    def fetch_borrow_rates(self, params={}):
        """
        fetch the borrow interest rates of all currencies
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns dict: a list of `borrow rate structures <https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure>`
        """
        self.load_markets()
        response = self.privateGetSpotMarginBorrowRates(params)
        #
        #     {
        #         "success":true,
        #         "result":[
        #             {"coin":"1INCH","previous":4.8763e-6,"estimate":4.8048e-6},
        #             {"coin":"AAPL","previous":0.0000326469,"estimate":0.0000326469},
        #             {"coin":"AAVE","previous":1.43e-6,"estimate":1.43e-6},
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result')
        return self.parse_borrow_rates(result, 'coin')

    def fetch_borrow_rate_histories(self, codes=None, since=None, limit=None, params={}):
        """
        retrieves a history of a multiple currencies borrow interest rate at specific time slots, returns all currencies if no symbols passed, default is None
        :param [str]|None codes: list of unified currency codes, default is None
        :param int|None since: timestamp in ms of the earliest borrowRate, default is None
        :param int|None limit: max number of borrow rate prices to return, default is None
        :param dict params: extra parameters specific to the ftx api endpoint
        :param dict params['till']: timestamp in ms of the latest time to fetch the borrow rate
        :returns dict: a dictionary of `borrow rate structures <https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure>` indexed by the market symbol
        """
        self.load_markets()
        request = {}
        numCodes = 0
        endTime = self.safe_number_2(params, 'till', 'end_time')
        if codes is not None:
            numCodes = len(codes)
        if numCodes == 1:
            millisecondsPer5000Hours = 18000000000
            if (limit is not None) and (limit > 5000):
                raise BadRequest(self.id + ' fetchBorrowRateHistories() limit cannot exceed 5000 for a single currency')
            if (endTime is not None) and (since is not None) and ((endTime - since) > millisecondsPer5000Hours):
                raise BadRequest(self.id + ' fetchBorrowRateHistories() requires the time range between the since time and the end time to be less than 5000 hours for a single currency')
            currency = self.currency(codes[0])
            request['coin'] = currency['id']
        else:
            millisecondsPer2Days = 172800000
            if (limit is not None) and (limit > 48):
                raise BadRequest(self.id + ' fetchBorrowRateHistories() limit cannot exceed 48 for multiple currencies')
            if (endTime is not None) and (since is not None) and ((endTime - since) > millisecondsPer2Days):
                raise BadRequest(self.id + ' fetchBorrowRateHistories() requires the time range between the since time and the end time to be less than 48 hours for multiple currencies')
        millisecondsPerHour = 3600000
        if since is not None:
            request['start_time'] = int(since / 1000)
            if endTime is None:
                now = self.milliseconds()
                sinceLimit = 2 if (limit is None) else limit
                endTime = self.sum(since, millisecondsPerHour * (sinceLimit - 1))
                endTime = min(endTime, now)
        else:
            if limit is not None:
                if endTime is None:
                    endTime = self.milliseconds()
                startTime = self.sum((endTime - millisecondsPerHour * limit), 1000)
                request['start_time'] = int(startTime / 1000)
        if endTime is not None:
            request['end_time'] = int(endTime / 1000)
        response = self.publicGetSpotMarginHistory(self.extend(request, params))
        #
        #    {
        #        "success": True,
        #        "result": [
        #            {
        #                "coin": "PYPL",
        #                "time": "2022-01-24T13:00:00+00:00",
        #                "size": 0.00500172,
        #                "rate": 1e-6
        #            },
        #            ...
        #        ]
        #    }
        #
        result = self.safe_value(response, 'result')
        return self.parse_borrow_rate_histories(result, codes, since, limit)

    def fetch_borrow_rate_history(self, code, since=None, limit=None, params={}):
        """
        retrieves a history of a currencies borrow interest rate at specific time slots
        :param str code: unified currency code
        :param int|None since: timestamp for the earliest borrow rate
        :param int|None limit: the maximum number of `borrow rate structures <https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure>` to retrieve
        :param dict params: extra parameters specific to the exchange api endpoint
        :param int|None params['till']: Timestamp in ms of the latest time to fetch the borrow rate
        :returns [dict]: an array of `borrow rate structures <https://docs.ccxt.com/en/latest/manual.html#borrow-rate-structure>`
        """
        histories = self.fetch_borrow_rate_histories([code], since, limit, params)
        borrowRateHistory = self.safe_value(histories, code)
        if borrowRateHistory is None:
            raise BadRequest(self.id + ' fetchBorrowRateHistory() returned no data for ' + code)
        return borrowRateHistory

    def parse_borrow_rate_histories(self, response, codes, since, limit):
        # How to calculate borrow rate
        # https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer
        takerFee = str(self.fees['trading']['taker'])
        spotMarginBorrowRate = Precise.string_mul('500', takerFee)
        borrowRateHistories = {}
        for i in range(0, len(response)):
            item = response[i]
            code = self.safe_currency_code(self.safe_string(item, 'coin'))
            if codes is None or self.in_array(code, codes):
                if not (code in borrowRateHistories):
                    borrowRateHistories[code] = []
                lendingRate = self.safe_string(item, 'rate')
                borrowRate = Precise.string_mul(lendingRate, Precise.string_add('1', spotMarginBorrowRate))
                borrowRateStructure = self.extend(self.parse_borrow_rate(item), {'rate': borrowRate})
                borrowRateHistories[code].append(borrowRateStructure)
        keys = list(borrowRateHistories.keys())
        for i in range(0, len(keys)):
            code = keys[i]
            borrowRateHistories[code] = self.filter_by_currency_since_limit(borrowRateHistories[code], code, since, limit)
        return borrowRateHistories

    def parse_borrow_rates(self, response, codeKey):
        result = {}
        for i in range(0, len(response)):
            item = response[i]
            currency = self.safe_string(item, codeKey)
            code = self.safe_currency_code(currency)
            borrowRate = self.parse_borrow_rate(item)
            result[code] = borrowRate
        return result

    def parse_borrow_rate(self, info, currency=None):
        #
        #    {
        #        "coin": "1INCH",
        #        "previous": 0.0000462375,
        #        "estimate": 0.0000462375
        #    }
        #
        coin = self.safe_string(info, 'coin')
        datetime = self.safe_string(info, 'time')
        timestamp = self.parse8601(datetime)
        return {
            'currency': self.safe_currency_code(coin),
            'rate': self.safe_number(info, 'previous'),
            'period': 3600000,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'info': info,
        }

    def fetch_borrow_interest(self, code=None, symbol=None, since=None, limit=None, params={}):
        """
        fetch the interest owed by the user for borrowing currency for margin trading
        :param str|None code: unified currency code
        :param str|None symbol: unified market symbol when fetch interest in isolated markets
        :param int|None since: the earliest time in ms to fetch borrrow interest for
        :param int|None limit: the maximum number of structures to retrieve
        :param dict params: extra parameters specific to the ftx api endpoint
        :returns [dict]: a list of `borrow interest structures <https://docs.ccxt.com/en/latest/manual.html#borrow-interest-structure>`
        """
        self.load_markets()
        request = {}
        if since is not None:
            request['start_time'] = int(since / 1000)
        response = self.privateGetSpotMarginBorrowHistory(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "result":[
        #             {"coin":"USDT","time":"2021-12-26T01:00:00+00:00","size":4593.74214725,"rate":3.3003e-6,"cost":0.0151607272085692,"feeUsd":0.0151683341034461},
        #             {"coin":"USDT","time":"2021-12-26T00:00:00+00:00","size":4593.97110361,"rate":3.3003e-6,"cost":0.0151614828332441,"feeUsd":0.015169697173028324},
        #             {"coin":"USDT","time":"2021-12-25T23:00:00+00:00","size":4594.20005922,"rate":3.3003e-6,"cost":0.0151622384554438,"feeUsd":0.015170200298479137},
        #         ]
        #     }
        #
        result = self.safe_value(response, 'result')
        interest = self.parse_borrow_interests(result)
        return self.filter_by_currency_since_limit(interest, code, since, limit)

    def parse_borrow_interest(self, info, market=None):
        coin = self.safe_string(info, 'coin')
        datetime = self.safe_string(info, 'time')
        return {
            'account': 'cross',
            'symbol': None,
            'marginMode': 'cross',
            'currency': self.safe_currency_code(coin),
            'interest': self.safe_number(info, 'cost'),
            'interestRate': self.safe_number(info, 'rate'),
            'amountBorrowed': self.safe_number(info, 'size'),
            'timestamp': self.parse8601(datetime),
            'datetime': datetime,
            'info': info,
        }

    def fetch_open_interest(self, symbol, params={}):
        """
        Retrieves the open interest of a currency
        see https://docs.ftx.com/#get-future-stats
        :param str symbol: Unified CCXT market symbol
        :param dict params: exchange specific parameters
        :returns dict} an open interest structure{@link https://docs.ccxt.com/en/latest/manual.html#interest-history-structure:
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['contract']:
            raise BadRequest(self.id + ' fetchOpenInterest() supports contract markets only')
        request = {
            'future_name': market['id'],
        }
        response = self.publicGetFuturesFutureNameStats(self.extend(request, params))
        #
        #     {
        #         "success": True,
        #         "result": {
        #             "volume": 207681.9947,
        #             "nextFundingRate": -5e-6,
        #             "nextFundingTime": "2022-09-30T22:00:00+00:00",
        #             "openInterest": 64745.8474
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        return self.parse_open_interest(result, market)

    def parse_open_interest(self, interest, market=None):
        #
        #     {
        #         "volume": 207681.9947,
        #         "nextFundingRate": -5e-6,
        #         "nextFundingTime": "2022-09-30T22:00:00+00:00",
        #         "openInterest": 64745.8474
        #     }
        #
        market = self.safe_market(None, market)
        openInterest = self.safe_number(interest, 'openInterest')
        return {
            'symbol': market['symbol'],
            'openInterestAmount': openInterest,
            'openInterestValue': None,
            'baseVolume': openInterest,  # deprecated
            'quoteVolume': None,  # deprecated
            'timestamp': None,
            'datetime': None,
            'info': interest,
        }
