<?php

namespace ccxt;

// 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

use Exception; // a common import
use \ccxt\ExchangeError;
use \ccxt\InvalidOrder;

class ftx extends Exchange {

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

    public function fetch_currencies($params = array ()) {
        $response = $this->publicGetCoins ($params);
        $currencies = $this->safe_value($response, 'result', array());
        //
        //     {
        //         "success":true,
        //         "$result" => array(
        //             array("$id":"BTC","$name":"Bitcoin"),
        //             array("$id":"ETH","$name":"Ethereum"),
        //             array("$id":"ETHMOON","$name":"10X Long Ethereum Token","underlying":"ETH"),
        //             array("$id":"EOSBULL","$name":"3X Long EOS Token","underlying":"EOS"),
        //         ),
        //     }
        //
        $result = array();
        for ($i = 0; $i < count($currencies); $i++) {
            $currency = $currencies[$i];
            $id = $this->safe_string($currency, 'id');
            $code = $this->safe_currency_code($id);
            $name = $this->safe_string($currency, 'name');
            $result[$code] = array(
                'id' => $id,
                'code' => $code,
                'info' => $currency,
                'type' => null,
                'name' => $name,
                'active' => null,
                'fee' => null,
                'precision' => null,
                'limits' => array(
                    'withdraw' => array( 'min' => null, 'max' => null ),
                    'amount' => array( 'min' => null, 'max' => null ),
                    'price' => array( 'min' => null, 'max' => null ),
                    'cost' => array( 'min' => null, 'max' => null ),
                ),
            );
        }
        return $result;
    }

    public function fetch_markets($params = array ()) {
        $response = $this->publicGetMarkets ($params);
        //
        //     {
        //         'success' => true,
        //         "$result" => array(
        //             array(
        //                 "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
        //             ),
        //             array(
        //                 "ask":170.44,
        //                 "baseCurrency":"ETH",
        //                 "bid":170.41,
        //                 "change1h":-0.018485459257126403,
        //                 "change24h":-0.023825887743413515,
        //                 "changeBod":-0.037605872388481086,
        //                 "enabled":true,
        //                 "last":172.72,
        //                 "name":"ETH/USD",
        //                 "price":170.44,
        //                 "$priceIncrement":0.01,
        //                 "quoteCurrency":"USD",
        //                 "quoteVolume24h":382802.0252,
        //                 "$sizeIncrement":0.001,
        //                 "$type":"spot",
        //                 "underlying":null,
        //                 "volumeUsd24h":382802.0252
        //             ),
        //         ),
        //     }
        //
        $result = array();
        $markets = $this->safe_value($response, 'result', array());
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $id = $this->safe_string($market, 'name');
            $baseId = $this->safe_string_2($market, 'baseCurrency', 'underlying');
            $quoteId = $this->safe_string($market, 'quoteCurrency', 'USD');
            $type = $this->safe_string($market, 'type');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            // check if a $market is a spot or future $market
            $symbol = ($type === 'future') ? $this->safe_string($market, 'name') : ($base . '/' . $quote);
            $active = $this->safe_value($market, 'enabled');
            $sizeIncrement = $this->safe_float($market, 'sizeIncrement');
            $priceIncrement = $this->safe_float($market, 'priceIncrement');
            $precision = array(
                'amount' => $sizeIncrement,
                'price' => $priceIncrement,
            );
            $result[] = array(
                'id' => $id,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'type' => $type,
                'future' => ($type === 'future'),
                'spot' => ($type === 'spot'),
                'active' => $active,
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => $sizeIncrement,
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => $priceIncrement,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'info' => $market,
            );
        }
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        //     {
        //         "ask":171.29,
        //         "baseCurrency":null, // $base currency for spot markets
        //         "bid":171.24,
        //         "change1h":-0.0012244897959183673,
        //         "change24h":-0.031603346901854366,
        //         "changeBod":-0.03297013492914808,
        //         "enabled":true,
        //         "$last":171.44,
        //         "name":"ETH-PERP",
        //         "price":171.29,
        //         "priceIncrement":0.01,
        //         "quoteCurrency":null, // $quote currency for spot markets
        //         "quoteVolume24h":8570651.12113,
        //         "sizeIncrement":0.001,
        //         "$type":"future",
        //         "underlying":"ETH", // null for spot markets
        //         "volumeUsd24h":8570651.12113,
        //     }
        //
        $symbol = null;
        $marketId = $this->safe_string($ticker, 'name');
        if (is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id)) {
            $market = $this->markets_by_id[$marketId];
        } else {
            $type = $this->safe_string($ticker, 'type');
            if ($type === 'future') {
                $symbol = $marketId;
            } else {
                $base = $this->safe_currency_code($this->safe_string($ticker, 'baseCurrency'));
                $quote = $this->safe_currency_code($this->safe_string($ticker, 'quoteCurrency'));
                if (($base !== null) && ($quote !== null)) {
                    $symbol = $base . '/' . $quote;
                }
            }
        }
        if (($symbol === null) && ($market !== null)) {
            $symbol = $market['symbol'];
        }
        $last = $this->safe_float($ticker, 'last');
        $timestamp = $this->safe_timestamp($ticker, 'time', $this->milliseconds());
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'high'),
            'low' => $this->safe_float($ticker, 'low'),
            'bid' => $this->safe_float($ticker, 'bid'),
            'bidVolume' => $this->safe_float($ticker, 'bidSize'),
            'ask' => $this->safe_float($ticker, 'ask'),
            'askVolume' => $this->safe_float($ticker, 'askSize'),
            'vwap' => null,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => $this->safe_float($ticker, 'change24h'),
            'average' => null,
            'baseVolume' => null,
            'quoteVolume' => $this->safe_float($ticker, 'quoteVolume24h'),
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_name' => $market['id'],
        );
        $response = $this->publicGetMarketsMarketName (array_merge($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 = $this->safe_value($response, 'result', array());
        return $this->parse_ticker($result, $market);
    }

    public function parse_tickers($tickers, $symbols = null) {
        $result = array();
        for ($i = 0; $i < count($tickers); $i++) {
            $result[] = $this->parse_ticker($tickers[$i]);
        }
        return $this->filter_by_array($result, 'symbol', $symbols);
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        $this->load_markets();
        $response = $this->publicGetMarkets ($params);
        //
        //     {
        //         'success' => true,
        //         "result" => array(
        //             array(
        //                 "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 = $this->safe_value($response, 'result', array());
        return $this->parse_tickers($tickers, $symbols);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_name' => $market['id'],
        );
        if ($limit !== null) {
            $request['depth'] = $limit; // max 100, default 20
        }
        $response = $this->publicGetMarketsMarketNameOrderbook (array_merge($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 = $this->safe_value($response, 'result', array());
        return $this->parse_order_book($result);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         "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 array(
            $this->safe_integer($ohlcv, 'time'),
            $this->safe_float($ohlcv, 'open'),
            $this->safe_float($ohlcv, 'high'),
            $this->safe_float($ohlcv, 'low'),
            $this->safe_float($ohlcv, 'close'),
            $this->safe_float($ohlcv, 'volume'),
        );
    }

    public function get_market_id($symbol, $key, $params = array ()) {
        $parts = $this->get_market_params($symbol, $key, $params);
        return $this->safe_string($parts, 1, $symbol);
    }

    public function get_market_params($symbol, $key, $params = array ()) {
        $market = null;
        $marketId = null;
        if (is_array($this->markets) && array_key_exists($symbol, $this->markets)) {
            $market = $this->market($symbol);
            $marketId = $market['id'];
        } else {
            $marketId = $this->safe_string($params, $key, $symbol);
        }
        return array( $market, $marketId );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        list($market, $marketId) = $this->get_market_params($symbol, 'market_name', $params);
        $request = array(
            'resolution' => $this->timeframes[$timeframe],
            'market_name' => $marketId,
        );
        // max 1501 candles, including the current candle when $since is not specified
        $limit = ($limit === null) ? 1501 : $limit;
        if ($since === null) {
            $request['end_time'] = $this->seconds();
            $request['limit'] = $limit;
            $request['start_time'] = $request['end_time'] - $limit * $this->parse_timeframe($timeframe);
        } else {
            $request['start_time'] = intval($since / 1000);
            $request['limit'] = $limit;
            $request['end_time'] = $this->sum($request['start_time'], $limit * $this->parse_timeframe($timeframe));
        }
        $response = $this->publicGetMarketsMarketNameCandles (array_merge($request, $params));
        //
        //     {
        //         "success" => true,
        //         "$result":array(
        //             array(
        //                 "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
        //             ),
        //             array(
        //                 "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 = $this->safe_value($response, 'result', array());
        return $this->parse_ohlcvs($result, $market, $timeframe, $since, $limit);
    }

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

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        list($market, $marketId) = $this->get_market_params($symbol, 'market_name', $params);
        $request = array(
            'market_name' => $marketId,
        );
        if ($since !== null) {
            $request['start_time'] = intval($since / 1000);
            // start_time doesn't work without end_time
            $request['end_time'] = $this->seconds();
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetMarketsMarketNameTrades (array_merge($request, $params));
        //
        //     {
        //         "success":true,
        //         "$result":array(
        //             array(
        //                 "id":1715826,
        //                 "liquidation":false,
        //                 "price":171.62,
        //                 "side":"buy",
        //                 "size":2.095,
        //                 "time":"2019-10-18T12:59:54.288166+00:00"
        //             ),
        //             array(
        //                 "id":1715763,
        //                 "liquidation":false,
        //                 "price":171.89,
        //                 "side":"sell",
        //                 "size":1.477,
        //                 "time":"2019-10-18T12:58:38.443734+00:00"
        //             ),
        //         ),
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_trades($result, $market, $since, $limit);
    }

    public function fetch_trading_fees($params = array ()) {
        $this->load_markets();
        $response = $this->privateGetAccount ($params);
        //
        //     {
        //         "success" => true,
        //         "$result" => array(
        //             "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" => array(
        //                 array(
        //                     "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 = $this->safe_value($response, 'result', array());
        return array(
            'info' => $response,
            'maker' => $this->safe_float($result, 'makerFee'),
            'taker' => $this->safe_float($result, 'takerFee'),
        );
    }

    public function fetch_balance($params = array ()) {
        $this->load_markets();
        $response = $this->privateGetWalletBalances ($params);
        //
        //     {
        //         "success" => true,
        //         "$result" => array(
        //             array(
        //                 "coin" => "USDTBEAR",
        //                 "free" => 2320.2,
        //                 "total" => 2340.2
        //             ),
        //         ),
        //     }
        //
        $result = array(
            'info' => $response,
        );
        $balances = $this->safe_value($response, 'result', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $code = $this->safe_currency_code($this->safe_string($balance, 'coin'));
            $account = $this->account();
            $account['free'] = $this->safe_float($balance, 'free');
            $account['total'] = $this->safe_float($balance, 'total');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function parse_order_status($status) {
        $statuses = array(
            'new' => 'open',
            'open' => 'open',
            'closed' => 'closed', // filled or canceled
            'triggered' => 'closed',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // 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 = $this->safe_string($order, 'id');
        $timestamp = $this->parse8601($this->safe_string($order, 'createdAt'));
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $amount = $this->safe_float($order, 'size');
        $filled = $this->safe_float($order, 'filledSize');
        $remaining = $this->safe_float($order, 'remainingSize');
        if (($remaining === 0.0) && ($amount !== null) && ($filled !== null)) {
            $remaining = max ($amount - $filled, 0);
            if ($remaining > 0) {
                $status = 'canceled';
            }
        }
        $symbol = null;
        $marketId = $this->safe_string($order, 'market');
        if ($marketId !== null) {
            if (is_array($this->markets_by_id) && array_key_exists($marketId, $this->markets_by_id)) {
                $market = $this->markets_by_id[$marketId];
                $symbol = $market['symbol'];
            } else {
                // support for delisted $market ids
                // https://github.com/ccxt/ccxt/issues/7113
                $symbol = $marketId;
            }
        }
        if (($symbol === null) && ($market !== null)) {
            $symbol = $market['symbol'];
        }
        $side = $this->safe_string($order, 'side');
        $type = $this->safe_string($order, 'type');
        $average = $this->safe_float($order, 'avgFillPrice');
        $price = $this->safe_float_2($order, 'price', 'triggerPrice', $average);
        $cost = null;
        if ($filled !== null && $price !== null) {
            $cost = $filled * $price;
        }
        $lastTradeTimestamp = $this->parse8601($this->safe_string($order, 'triggeredAt'));
        $clientOrderId = $this->safe_string($order, 'clientId');
        $stopPrice = $this->safe_float($order, 'triggerPrice');
        $postOnly = $this->safe_value($order, 'postOnly');
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => null,
            'trades' => null,
        );
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
            'side' => $side, // "buy" or "sell"
            // 'price' => 0.306525, // send null for $market orders
            'type' => $type, // "limit", "$market", "stop", "trailingStop", or "takeProfit"
            'size' => floatval($this->amount_to_precision($symbol, $amount)),
            // 'reduceOnly' => false, // optional, default is false
            // 'ioc' => false, // optional, default is false, limit or $market orders only
            // 'postOnly' => false, // optional, default is false, limit or $market orders only
            // 'clientId' => 'abcdef0123456789', // string, optional, client order id, limit or $market orders only
        );
        $clientOrderId = $this->safe_string_2($params, 'clientId', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['clientId'] = $clientOrderId;
            $params = $this->omit($params, array( 'clientId', 'clientOrderId' ));
        }
        $priceToPrecision = null;
        if ($price !== null) {
            $priceToPrecision = floatval($this->price_to_precision($symbol, $price));
        }
        $method = 'privatePostConditionalOrders';
        if ($type === 'limit') {
            $method = 'privatePostOrders';
            $request['price'] = $priceToPrecision;
        } else if ($type === 'market') {
            $method = 'privatePostOrders';
            $request['price'] = null;
        } else if (($type === 'stop') || ($type === 'takeProfit')) {
            $request['triggerPrice'] = $priceToPrecision;
            // $request['orderPrice'] = number; // optional, order $type is limit if this is specified, otherwise $market
        } else if ($type === 'trailingStop') {
            $request['trailValue'] = $priceToPrecision; // negative for "sell", positive for "buy"
        } else {
            throw new InvalidOrder($this->id . ' createOrder () does not support order $type ' . $type . ', only limit, $market, stop, trailingStop, or takeProfit orders are supported');
        }
        $response = $this->$method (array_merge($request, $params));
        //
        // orders
        //
        //     {
        //         "success" => true,
        //         "$result" => array(
        //             {
        //                 "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" => array(
        //             {
        //                 "createdAt" => "2019-03-05T09:56:55.728933+00:00",
        //                 "future" => "XRP-PERP",
        //                 "id" => 9596912,
        //                 "$market" => "XRP-PERP",
        //                 "triggerPrice" => 0.306525,
        //                 "orderId" => null,
        //                 "$side" => "sell",
        //                 "size" => 31431,
        //                 "status" => "open",
        //                 "$type" => "stop",
        //                 "orderPrice" => null,
        //                 "error" => null,
        //                 "triggeredAt" => null,
        //                 "reduceOnly" => false
        //             }
        //         )
        //     }
        //
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_order($result, $market);
    }

    public function edit_order($id, $symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array();
        $method = null;
        $clientOrderId = $this->safe_string_2($params, 'client_order_id', 'clientOrderId');
        $triggerPrice = $this->safe_float($params, 'triggerPrice');
        $orderPrice = $this->safe_float($params, 'orderPrice');
        $trailValue = $this->safe_float($params, 'trailValue');
        $params = $this->omit($params, array( 'client_order_id', 'clientOrderId', 'triggerPrice', 'orderPrice', 'trailValue' ));
        $triggerPriceIsDefined = ($triggerPrice !== null);
        $orderPriceIsDefined = ($orderPrice !== null);
        $trailValueIsDefined = ($trailValue !== null);
        if ($triggerPriceIsDefined || $orderPriceIsDefined || $trailValueIsDefined) {
            $method = 'privatePostConditionalOrdersOrderIdModify';
            $request['order_id'] = $id;
            if ($triggerPriceIsDefined) {
                $request['triggerPrice'] = floatval($this->price_to_precision($symbol, $triggerPrice));
            }
            if ($orderPriceIsDefined) {
                // only for stop limit or take profit limit orders
                $request['orderPrice'] = floatval($this->price_to_precision($symbol, $orderPrice));
            }
            if ($trailValueIsDefined) {
                // negative for sell orders, positive for buy orders
                $request['trailValue'] = floatval($this->price_to_precision($symbol, $trailValue));
            }
        } else {
            if ($clientOrderId === null) {
                $method = 'privatePostOrdersOrderIdModify';
                $request['order_id'] = $id;
            } else {
                $method = 'privatePostOrdersByClientIdClientOrderIdModify';
                $request['client_order_id'] = $clientOrderId;
                // $request['clientId'] = $clientOrderId;
            }
            if ($price !== null) {
                $request['price'] = floatval($this->price_to_precision($symbol, $price));
            }
        }
        if ($amount !== null) {
            $request['size'] = floatval($this->amount_to_precision($symbol, $amount));
        }
        $response = $this->$method (array_merge($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 = $this->safe_value($response, 'result', array());
        return $this->parse_order($result, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'order_id' => intval($id),
        );
        // support for canceling conditional orders
        // https://github.com/ccxt/ccxt/issues/6669
        $options = $this->safe_value($this->options, 'cancelOrder', array());
        $defaultMethod = $this->safe_string($options, 'method', 'privateDeleteOrdersOrderId');
        $method = $this->safe_string($params, 'method', $defaultMethod);
        $type = $this->safe_value($params, 'type');
        $clientOrderId = $this->safe_value_2($params, 'client_order_id', 'clientOrderId');
        if ($clientOrderId === null) {
            $request['order_id'] = intval($id);
            if (($type === 'stop') || ($type === 'trailingStop') || ($type === 'takeProfit')) {
                $method = 'privateDeleteConditionalOrdersOrderId';
            }
        } else {
            $request['client_order_id'] = $clientOrderId;
            $method = 'privateDeleteOrdersByClientIdClientOrderId';
        }
        $query = $this->omit($params, array( 'method', 'type', 'client_order_id', 'clientOrderId' ));
        $response = $this->$method (array_merge($request, $query));
        //
        //     {
        //         "success" => true,
        //         "$result" => "Order queued for cancelation"
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $result;
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        $this->load_markets();
        $conditionalOrdersOnly = $this->safe_value($params, 'conditionalOrdersOnly');
        $request = array(
            // 'market' => market['id'], // optional
            // 'conditionalOrdersOnly' => false, // cancel conditional orders only
            // 'limitOrdersOnly' => false, // cancel existing limit orders (non-conditional orders) only
        );
        if ($conditionalOrdersOnly) {
            $request['conditionalOrdersOnly'] = $conditionalOrdersOnly;
        } else {
            $request['limitOrdersOnly'] = true;
        }
        $marketId = $this->get_market_id($symbol, 'market', $params);
        if ($marketId !== null) {
            $request['market'] = $marketId;
        }
        $response = $this->privateDeleteOrders (array_merge($request, $params));
        $result = $this->safe_value($response, 'result', array());
        //
        //     {
        //         "success" => true,
        //         "$result" => "Orders queued for cancelation"
        //     }
        //
        return $result;
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $request = array();
        $clientOrderId = $this->safe_value_2($params, 'client_order_id', 'clientOrderId');
        $method = 'privateGetOrdersOrderId';
        if ($clientOrderId === null) {
            $request['order_id'] = $id;
        } else {
            $request['client_order_id'] = $clientOrderId;
            $params = $this->omit($params, [ 'client_order_id', 'clientOrderId']);
            $method = 'privateGetOrdersByClientIdClientOrderId';
        }
        $response = $this->$method (array_merge($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 = $this->safe_value($response, 'result', array());
        return $this->parse_order($result);
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array();
        list($market, $marketId) = $this->get_market_params($symbol, 'market', $params);
        if ($marketId !== null) {
            $request['market'] = $marketId;
        }
        // support for canceling conditional orders
        // https://github.com/ccxt/ccxt/issues/6669
        $options = $this->safe_value($this->options, 'fetchOpenOrders', array());
        $defaultMethod = $this->safe_string($options, 'method', 'privateGetOrders');
        $method = $this->safe_string($params, 'method', $defaultMethod);
        $type = $this->safe_value($params, 'type');
        if (($type === 'stop') || ($type === 'trailingStop') || ($type === 'takeProfit')) {
            $method = 'privateGetConditionalOrders';
        }
        $query = $this->omit($params, array( 'method', 'type' ));
        $response = $this->$method (array_merge($request, $query));
        //
        //     {
        //         "success" => true,
        //         "$result" => array(
        //             {
        //                 "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 = $this->safe_value($response, 'result', array());
        return $this->parse_orders($result, $market, $since, $limit);
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array();
        list($market, $marketId) = $this->get_market_params($symbol, 'market', $params);
        if ($marketId !== null) {
            $request['market'] = $marketId;
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 100, max 100
        }
        if ($since !== null) {
            $request['start_time'] = intval($since / 1000);
        }
        // support for canceling conditional orders
        // https://github.com/ccxt/ccxt/issues/6669
        $options = $this->safe_value($this->options, 'fetchOrders', array());
        $defaultMethod = $this->safe_string($options, 'method', 'privateGetOrdersHistory');
        $method = $this->safe_string($params, 'method', $defaultMethod);
        $type = $this->safe_value($params, 'type');
        if (($type === 'stop') || ($type === 'trailingStop') || ($type === 'takeProfit')) {
            $method = 'privateGetConditionalOrdersHistory';
        }
        $query = $this->omit($params, array( 'method', 'type' ));
        $response = $this->$method (array_merge($request, $query));
        //
        //     {
        //         "success" => true,
        //         "$result" => array(
        //             {
        //                 "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 = $this->safe_value($response, 'result', array());
        return $this->parse_orders($result, $market, $since, $limit);
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        list($market, $marketId) = $this->get_market_params($symbol, 'market', $params);
        $request = array();
        if ($marketId !== null) {
            $request['market'] = $marketId;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        if ($since !== null) {
            $request['start_time'] = intval($since / 1000);
            $request['end_time'] = $this->seconds();
        }
        $response = $this->privateGetFills (array_merge($request, $params));
        //
        //     {
        //         "success" => true,
        //         "result" => array(
        //             {
        //                 "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 = $this->safe_value($response, 'result', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function withdraw($code, $amount, $address, $tag = null, $params = array ()) {
        $this->load_markets();
        $this->check_address($address);
        $currency = $this->currency($code);
        $request = array(
            '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 ($this->password !== null) {
            $request['password'] = $this->password;
        }
        if ($tag !== null) {
            $request['tag'] = $tag;
        }
        $response = $this->privatePostWalletWithdrawals (array_merge($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 = $this->safe_value($response, 'result', array());
        return $this->parse_transaction($result, $currency);
    }

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

    public function fetch_deposit_address($code, $params = array ()) {
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'coin' => $currency['id'],
        );
        $response = $this->privateGetWalletDepositAddressCoin (array_merge($request, $params));
        //
        //     {
        //         "success" => true,
        //         "$result" => {
        //             "$address" => "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
        //             "$tag" => "null"
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $address = $this->safe_string($result, 'address');
        $tag = $this->safe_string($result, 'tag');
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => $tag,
            'info' => $response,
        );
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            // what are other $statuses here?
            'confirmed' => 'ok', // deposits
            'complete' => 'ok', // withdrawals
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // fetchDeposits
        //
        //     {
        //         "coin" => "TUSD",
        //         "confirmations" => 64,
        //         "confirmedTime" => "2019-03-05T09:56:55.728933+00:00",
        //         "$fee" => 0,
        //         "$id" => 1,
        //         "sentTime" => "2019-03-05T09:56:55.735929+00:00",
        //         "size" => "99.0",
        //         "$status" => "confirmed",
        //         "time" => "2019-03-05T09:56:55.728933+00:00",
        //         "$txid" => "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "coin" => "TUSD",
        //         "$address" => "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
        //         "$tag" => "null",
        //         "$fee" => 0,
        //         "$id" => 1,
        //         "size" => "99.0",
        //         "$status" => "complete",
        //         "time" => "2019-03-05T09:56:55.728933+00:00",
        //         "$txid" => "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
        //     }
        //
        //     {
        //         "coin" => 'BTC',
        //         "$id" => 1969806,
        //         "$notes" => 'Transfer to Dd6gi7m2Eg4zzBbPAxuwfEaHs6tYvyUX5hbPpsTcNPXo',
        //         "size" => 0.003,
        //         "$status" => 'complete',
        //         "time" => '2021-02-03T20:28:54.918146+00:00'
        //     }
        //
        $code = $this->safe_currency_code($this->safe_string($transaction, 'coin'));
        $id = $this->safe_string($transaction, 'id');
        $amount = $this->safe_float($transaction, 'size');
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $timestamp = $this->parse8601($this->safe_string($transaction, 'time'));
        $txid = $this->safe_string($transaction, 'txid');
        $tag = null;
        $address = $this->safe_value($transaction, 'address');
        if (gettype($address) !== 'string') {
            $tag = $this->safe_string($address, 'tag');
            $address = $this->safe_string($address, 'address');
        }
        if ($address === null) {
            // parse $address from internal transfer
            $notes = $this->safe_string($transaction, 'notes');
            if ($notes !== null) {
                $address = mb_substr($notes, 12);
            }
        }
        $fee = $this->safe_float($transaction, 'fee');
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'addressFrom' => null,
            'address' => $address,
            'addressTo' => $address,
            'tagFrom' => null,
            'tag' => $tag,
            'tagTo' => $tag,
            'type' => null,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'fee' => array(
                'currency' => $code,
                'cost' => $fee,
                'rate' => null,
            ),
        );
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $response = $this->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 = $this->safe_value($response, 'result', array());
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        return $this->parse_transactions($result, $currency, $since, $limit, array( 'type' => 'deposit' ));
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $response = $this->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 = $this->safe_value($response, 'result', array());
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        return $this->parse_transactions($result, $currency, $since, $limit, array( 'type' => 'withdrawal' ));
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $request = '/api/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        $baseUrl = $this->implode_params($this->urls['api'][$api], array( 'hostname' => $this->hostname ));
        $url = $baseUrl . $request;
        if ($method !== 'POST') {
            if ($query) {
                $suffix = '?' . $this->urlencode($query);
                $url .= $suffix;
                $request .= $suffix;
            }
        }
        if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = (string) $this->milliseconds();
            $auth = $timestamp . $method . $request;
            $headers = array();
            if (($method === 'POST') || ($method === 'DELETE')) {
                $body = $this->json($query);
                $auth .= $body;
                $headers['Content-Type'] = 'application/json';
            }
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
            $options = $this->safe_value($this->options, 'sign', array());
            $headerPrefix = $this->safe_string($options, $this->hostname, 'FTX');
            $keyField = $headerPrefix . '-KEY';
            $tsField = $headerPrefix . '-TS';
            $signField = $headerPrefix . '-SIGN';
            $headers[$keyField] = $this->apiKey;
            $headers[$tsField] = $timestamp;
            $headers[$signField] = $signature;
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors($code, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return; // fallback to the default $error handler
        }
        //
        //     array("$error":"Invalid parameter start_time","$success":false)
        //     array("$error":"Not enough balances","$success":false)
        //
        $success = $this->safe_value($response, 'success');
        if (!$success) {
            $feedback = $this->id . ' ' . $body;
            $error = $this->safe_string($response, 'error');
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $error, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $error, $feedback);
            throw new ExchangeError($feedback); // unknown message
        }
    }
}
