<?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\ArgumentsRequired;

class bybit extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'bybit',
            'name' => 'Bybit',
            'countries' => array( 'VG' ), // British Virgin Islands
            'version' => 'v2',
            'userAgent' => null,
            'rateLimit' => 100,
            'hostname' => 'bybit.com', // bybit.com, bytick.com
            'has' => array(
                'cancelOrder' => true,
                'CORS' => true,
                'cancelAllOrders' => true,
                'createOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchDeposits' => true,
                'fetchLedger' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchOrderTrades' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTransactions' => false,
                'fetchWithdrawals' => true,
            ),
            'timeframes' => array(
                '1m' => '1',
                '3m' => '3',
                '5m' => '5',
                '15m' => '15',
                '30m' => '30',
                '1h' => '60',
                '2h' => '120',
                '4h' => '240',
                '6h' => '360',
                '12h' => '720',
                '1d' => 'D',
                '1w' => 'W',
                '1M' => 'M',
                '1y' => 'Y',
            ),
            'urls' => array(
                'test' => 'https://api-testnet.{hostname}',
                'logo' => 'https://user-images.githubusercontent.com/51840849/76547799-daff5b80-649e-11ea-87fb-3be9bac08954.jpg',
                'api' => 'https://api.{hostname}',
                'www' => 'https://www.bybit.com',
                'doc' => array(
                    'https://bybit-exchange.github.io/docs/inverse/',
                    'https://bybit-exchange.github.io/docs/linear/',
                    'https://github.com/bybit-exchange',
                ),
                'fees' => 'https://help.bybit.com/hc/en-us/articles/360039261154',
                'referral' => 'https://www.bybit.com/app/register?ref=X7Prm',
            ),
            'api' => array(
                'v2' => array(
                    'public' => array(
                        'get' => array(
                            'orderBook/L2',
                            'kline/list',
                            'tickers',
                            'trading-records',
                            'symbols',
                            'liq-records',
                            'mark-price-kline',
                            'index-price-kline',
                            'premium-index-kline',
                            'open-interest',
                            'big-deal',
                            'account-ratio',
                            'time',
                            'announcement',
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'order/list',
                            'order',
                            'stop-order/list',
                            'stop-order',
                            'position/list',
                            'execution/list',
                            'trade/closed-pnl/list',
                            'funding/prev-funding-rate',
                            'funding/prev-funding',
                            'funding/predicted-funding',
                            'account/api-key',
                            'account/lcp',
                            'wallet/balance',
                            'wallet/fund/records',
                            'wallet/withdraw/list',
                            'exchange-order/list',
                        ),
                        'post' => array(
                            'order/create',
                            'order/cancel',
                            'order/cancelAll',
                            'order/replace',
                            'stop-order/create',
                            'stop-order/cancel',
                            'stop-order/cancelAll',
                            'stop-order/replace',
                            'position/change-position-margin',
                            'position/trading-stop',
                            'position/leverage/save',
                        ),
                    ),
                ),
                'public' => array(
                    'linear' => array(
                        'get' => array(
                            'kline',
                            'recent-trading-records',
                            'funding/prev-funding-rate',
                            'mark-price-kline',
                            'index-price-kline',
                            'premium-index-kline',
                            'risk-limit',
                        ),
                    ),
                ),
                'private' => array(
                    'linear' => array(
                        'get' => array(
                            'order/list',
                            'order/search',
                            'stop-order/list',
                            'stop-order/search',
                            'position/list',
                            'trade/execution/list',
                            'trade/closed-pnl/list',
                            'funding/predicted-funding',
                            'funding/prev-funding',
                        ),
                        'post' => array(
                            'order/create',
                            'order/cancel',
                            'order/cancel-all',
                            'order/replace',
                            'stop-order/create',
                            'stop-order/cancel',
                            'stop-order/cancel-all',
                            'stop-order/replace',
                            'position/set-auto-add-margin',
                            'position/switch-isolated',
                            'tpsl/switch-mode',
                            'position/add-margin',
                            'position/set-leverage',
                            'position/trading-stop',
                        ),
                    ),
                ),
                'openapi' => array(
                    'wallet' => array(
                        'get' => array(
                            'risk-limit/list',
                        ),
                        'post' => array(
                            'risk-limit',
                        ),
                    ),
                ),
            ),
            'httpExceptions' => array(
                '403' => '\\ccxt\\RateLimitExceeded', // Forbidden -- You request too many times
            ),
            'exceptions' => array(
                'exact' => array(
                    '10001' => '\\ccxt\\BadRequest', // parameter error
                    '10002' => '\\ccxt\\InvalidNonce', // request expired, check your timestamp and recv_window
                    '10003' => '\\ccxt\\AuthenticationError', // Invalid apikey
                    '10004' => '\\ccxt\\AuthenticationError', // invalid sign
                    '10005' => '\\ccxt\\PermissionDenied', // permission denied for current apikey
                    '10006' => '\\ccxt\\RateLimitExceeded', // too many requests
                    '10007' => '\\ccxt\\AuthenticationError', // api_key not found in your request parameters
                    '10010' => '\\ccxt\\PermissionDenied', // request ip mismatch
                    '10017' => '\\ccxt\\BadRequest', // request path not found or request method is invalid
                    '20001' => '\\ccxt\\OrderNotFound', // Order not exists
                    '20003' => '\\ccxt\\InvalidOrder', // missing parameter side
                    '20004' => '\\ccxt\\InvalidOrder', // invalid parameter side
                    '20005' => '\\ccxt\\InvalidOrder', // missing parameter symbol
                    '20006' => '\\ccxt\\InvalidOrder', // invalid parameter symbol
                    '20007' => '\\ccxt\\InvalidOrder', // missing parameter order_type
                    '20008' => '\\ccxt\\InvalidOrder', // invalid parameter order_type
                    '20009' => '\\ccxt\\InvalidOrder', // missing parameter qty
                    '20010' => '\\ccxt\\InvalidOrder', // qty must be greater than 0
                    '20011' => '\\ccxt\\InvalidOrder', // qty must be an integer
                    '20012' => '\\ccxt\\InvalidOrder', // qty must be greater than zero and less than 1 million
                    '20013' => '\\ccxt\\InvalidOrder', // missing parameter price
                    '20014' => '\\ccxt\\InvalidOrder', // price must be greater than 0
                    '20015' => '\\ccxt\\InvalidOrder', // missing parameter time_in_force
                    '20016' => '\\ccxt\\InvalidOrder', // invalid value for parameter time_in_force
                    '20017' => '\\ccxt\\InvalidOrder', // missing parameter order_id
                    '20018' => '\\ccxt\\InvalidOrder', // invalid date format
                    '20019' => '\\ccxt\\InvalidOrder', // missing parameter stop_px
                    '20020' => '\\ccxt\\InvalidOrder', // missing parameter base_price
                    '20021' => '\\ccxt\\InvalidOrder', // missing parameter stop_order_id
                    '20022' => '\\ccxt\\BadRequest', // missing parameter leverage
                    '20023' => '\\ccxt\\BadRequest', // leverage must be a number
                    '20031' => '\\ccxt\\BadRequest', // leverage must be greater than zero
                    '20070' => '\\ccxt\\BadRequest', // missing parameter margin
                    '20071' => '\\ccxt\\BadRequest', // margin must be greater than zero
                    '20084' => '\\ccxt\\BadRequest', // order_id or order_link_id is required
                    '30001' => '\\ccxt\\BadRequest', // order_link_id is repeated
                    '30003' => '\\ccxt\\InvalidOrder', // qty must be more than the minimum allowed
                    '30004' => '\\ccxt\\InvalidOrder', // qty must be less than the maximum allowed
                    '30005' => '\\ccxt\\InvalidOrder', // price exceeds maximum allowed
                    '30007' => '\\ccxt\\InvalidOrder', // price exceeds minimum allowed
                    '30008' => '\\ccxt\\InvalidOrder', // invalid order_type
                    '30009' => '\\ccxt\\ExchangeError', // no position found
                    '30010' => '\\ccxt\\InsufficientFunds', // insufficient wallet balance
                    '30011' => '\\ccxt\\PermissionDenied', // operation not allowed as position is undergoing liquidation
                    '30012' => '\\ccxt\\PermissionDenied', // operation not allowed as position is undergoing ADL
                    '30013' => '\\ccxt\\PermissionDenied', // position is in liq or adl status
                    '30014' => '\\ccxt\\InvalidOrder', // invalid closing order, qty should not greater than size
                    '30015' => '\\ccxt\\InvalidOrder', // invalid closing order, side should be opposite
                    '30016' => '\\ccxt\\ExchangeError', // TS and SL must be cancelled first while closing position
                    '30017' => '\\ccxt\\InvalidOrder', // estimated fill price cannot be lower than current Buy liq_price
                    '30018' => '\\ccxt\\InvalidOrder', // estimated fill price cannot be higher than current Sell liq_price
                    '30019' => '\\ccxt\\InvalidOrder', // cannot attach TP/SL params for non-zero position when placing non-opening position order
                    '30020' => '\\ccxt\\InvalidOrder', // position already has TP/SL params
                    '30021' => '\\ccxt\\InvalidOrder', // cannot afford estimated position_margin
                    '30022' => '\\ccxt\\InvalidOrder', // estimated buy liq_price cannot be higher than current mark_price
                    '30023' => '\\ccxt\\InvalidOrder', // estimated sell liq_price cannot be lower than current mark_price
                    '30024' => '\\ccxt\\InvalidOrder', // cannot set TP/SL/TS for zero-position
                    '30025' => '\\ccxt\\InvalidOrder', // trigger price should bigger than 10% of last price
                    '30026' => '\\ccxt\\InvalidOrder', // price too high
                    '30027' => '\\ccxt\\InvalidOrder', // price set for Take profit should be higher than Last Traded Price
                    '30028' => '\\ccxt\\InvalidOrder', // price set for Stop loss should be between Liquidation price and Last Traded Price
                    '30029' => '\\ccxt\\InvalidOrder', // price set for Stop loss should be between Last Traded Price and Liquidation price
                    '30030' => '\\ccxt\\InvalidOrder', // price set for Take profit should be lower than Last Traded Price
                    '30031' => '\\ccxt\\InsufficientFunds', // insufficient available balance for order cost
                    '30032' => '\\ccxt\\InvalidOrder', // order has been filled or cancelled
                    '30033' => '\\ccxt\\RateLimitExceeded', // The number of stop orders exceeds maximum limit allowed
                    '30034' => '\\ccxt\\OrderNotFound', // no order found
                    '30035' => '\\ccxt\\RateLimitExceeded', // too fast to cancel
                    '30036' => '\\ccxt\\ExchangeError', // the expected position value after order execution exceeds the current risk limit
                    '30037' => '\\ccxt\\InvalidOrder', // order already cancelled
                    '30041' => '\\ccxt\\ExchangeError', // no position found
                    '30042' => '\\ccxt\\InsufficientFunds', // insufficient wallet balance
                    '30043' => '\\ccxt\\PermissionDenied', // operation not allowed as position is undergoing liquidation
                    '30044' => '\\ccxt\\PermissionDenied', // operation not allowed as position is undergoing AD
                    '30045' => '\\ccxt\\PermissionDenied', // operation not allowed as position is not normal status
                    '30049' => '\\ccxt\\InsufficientFunds', // insufficient available balance
                    '30050' => '\\ccxt\\ExchangeError', // any adjustments made will trigger immediate liquidation
                    '30051' => '\\ccxt\\ExchangeError', // due to risk limit, cannot adjust leverage
                    '30052' => '\\ccxt\\ExchangeError', // leverage can not less than 1
                    '30054' => '\\ccxt\\ExchangeError', // position margin is invalid
                    '30057' => '\\ccxt\\ExchangeError', // requested quantity of contracts exceeds risk limit
                    '30063' => '\\ccxt\\ExchangeError', // reduce-only rule not satisfied
                    '30067' => '\\ccxt\\InsufficientFunds', // insufficient available balance
                    '30068' => '\\ccxt\\ExchangeError', // exit value must be positive
                    '34026' => '\\ccxt\\ExchangeError', // the limit is no change
                ),
                'broad' => array(
                    'unknown orderInfo' => '\\ccxt\\OrderNotFound', // array("ret_code":-1,"ret_msg":"unknown orderInfo","ext_code":"","ext_info":"","result":null,"time_now":"1584030414.005545","rate_limit_status":99,"rate_limit_reset_ms":1584030414003,"rate_limit":100)
                    'invalid api_key' => '\\ccxt\\AuthenticationError', // array("ret_code":10003,"ret_msg":"invalid api_key","ext_code":"","ext_info":"","result":null,"time_now":"1599547085.415797")
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                'marketTypes' => array(
                    'BTC/USDT' => 'linear',
                    'BCH/USDT' => 'linear',
                    'ETH/USDT' => 'linear',
                    'LTC/USDT' => 'linear',
                    'XTZ/USDT' => 'linear',
                    'LINK/USDT' => 'linear',
                ),
                'code' => 'BTC',
                'cancelAllOrders' => array(
                    // 'method' => 'v2PrivatePostOrderCancelAll', // v2PrivatePostStopOrderCancelAll
                ),
                'recvWindow' => 5 * 1000, // 5 sec default
                'timeDifference' => 0, // the difference between system clock and Binance clock
                'adjustForTimeDifference' => false, // controls the adjustment logic upon instantiation
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'taker' => 0.00075,
                    'maker' => -0.00025,
                ),
                'funding' => array(
                    'tierBased' => false,
                    'percentage' => false,
                    'withdraw' => array(),
                    'deposit' => array(),
                ),
            ),
        ));
    }

    public function nonce() {
        return $this->milliseconds() - $this->options['timeDifference'];
    }

    public function load_time_difference($params = array ()) {
        $serverTime = $this->fetch_time($params);
        $after = $this->milliseconds();
        $this->options['timeDifference'] = $after - $serverTime;
        return $this->options['timeDifference'];
    }

    public function fetch_time($params = array ()) {
        $response = $this->v2PublicGetTime ($params);
        //
        //     {
        //         ret_code => 0,
        //         ret_msg => 'OK',
        //         ext_code => '',
        //         ext_info => '',
        //         result => array(),
        //         time_now => '1583933682.448826'
        //     }
        //
        return $this->safe_timestamp($response, 'time_now');
    }

    public function fetch_markets($params = array ()) {
        if ($this->options['adjustForTimeDifference']) {
            $this->load_time_difference();
        }
        $response = $this->v2PublicGetSymbols ($params);
        //
        //     {
        //         "ret_code":0,
        //         "ret_msg":"OK",
        //         "ext_code":"",
        //         "ext_info":"",
        //         "$result":array(
        //             array(
        //                 "name":"BTCUSD",
        //                 "alias":"BTCUSD",
        //                 "$status":"Trading",
        //                 "base_currency":"BTC",
        //                 "quote_currency":"USD",
        //                 "price_scale":2,
        //                 "taker_fee":"0.00075",
        //                 "maker_fee":"-0.00025",
        //                 "leverage_filter":array("min_leverage":1,"max_leverage":100,"leverage_step":"0.01"),
        //                 "price_filter":array("min_price":"0.5","max_price":"999999.5","tick_size":"0.5"),
        //                 "lot_size_filter":array("max_trading_qty":1000000,"min_trading_qty":1,"qty_step":1)
        //             ),
        //             array(
        //                 "name":"BTCUSDT",
        //                 "alias":"BTCUSDT",
        //                 "$status":"Trading",
        //                 "base_currency":"BTC",
        //                 "quote_currency":"USDT",
        //                 "price_scale":2,
        //                 "taker_fee":"0.00075",
        //                 "maker_fee":"-0.00025",
        //                 "leverage_filter":array("min_leverage":1,"max_leverage":100,"leverage_step":"0.01"),
        //                 "price_filter":array("min_price":"0.5","max_price":"999999.5","tick_size":"0.5"),
        //                 "lot_size_filter":array("max_trading_qty":100,"min_trading_qty":0.001,"qty_step":0.001)
        //             ),
        //         ),
        //         "time_now":"1610539664.818033"
        //     }
        //
        $markets = $this->safe_value($response, 'result', array());
        $options = $this->safe_value($this->options, 'fetchMarkets', array());
        $linearQuoteCurrencies = $this->safe_value($options, 'linear', array( 'USDT' => true ));
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $id = $this->safe_string_2($market, 'name', 'symbol');
            $baseId = $this->safe_string($market, 'base_currency');
            $quoteId = $this->safe_string($market, 'quote_currency');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $linear = (is_array($linearQuoteCurrencies) && array_key_exists($quote, $linearQuoteCurrencies));
            $inverse = !$linear;
            $symbol = $base . '/' . $quote;
            $baseQuote = $base . $quote;
            if ($baseQuote !== $id) {
                $symbol = $id;
            }
            $lotSizeFilter = $this->safe_value($market, 'lot_size_filter', array());
            $priceFilter = $this->safe_value($market, 'price_filter', array());
            $precision = array(
                'amount' => $this->safe_float($lotSizeFilter, 'qty_step'),
                'price' => $this->safe_float($priceFilter, 'tick_size'),
            );
            $status = $this->safe_string($market, 'status');
            $active = null;
            if ($status !== null) {
                $active = ($status === 'Trading');
            }
            $result[] = array(
                'id' => $id,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'active' => $active,
                'precision' => $precision,
                'taker' => $this->safe_float($market, 'taker_fee'),
                'maker' => $this->safe_float($market, 'maker_fee'),
                'type' => 'future',
                'spot' => false,
                'future' => true,
                'option' => false,
                'linear' => $linear,
                'inverse' => $inverse,
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($lotSizeFilter, 'min_trading_qty'),
                        'max' => $this->safe_float($lotSizeFilter, 'max_trading_qty'),
                    ),
                    'price' => array(
                        'min' => $this->safe_float($priceFilter, 'min_price'),
                        'max' => $this->safe_float($priceFilter, 'max_price'),
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'info' => $market,
            );
        }
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker
        //
        //     {
        //         $symbol => 'BTCUSD',
        //         bid_price => '7680',
        //         ask_price => '7680.5',
        //         last_price => '7680.00',
        //         last_tick_direction => 'MinusTick',
        //         prev_price_24h => '7870.50',
        //         price_24h_pcnt => '-0.024204',
        //         high_price_24h => '8035.00',
        //         low_price_24h => '7671.00',
        //         prev_price_1h => '7780.00',
        //         price_1h_pcnt => '-0.012853',
        //         mark_price => '7683.27',
        //         index_price => '7682.74',
        //         open_interest => 188829147,
        //         open_value => '23670.06',
        //         total_turnover => '25744224.90',
        //         turnover_24h => '102997.83',
        //         total_volume => 225448878806,
        //         volume_24h => 809919408,
        //         funding_rate => '0.0001',
        //         predicted_funding_rate => '0.0001',
        //         next_funding_time => '2020-03-12T00:00:00Z',
        //         countdown_hour => 7
        //     }
        //
        $timestamp = null;
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $last = $this->safe_float($ticker, 'last_price');
        $open = $this->safe_float($ticker, 'prev_price_24h');
        $percentage = $this->safe_float($ticker, 'price_24h_pcnt');
        if ($percentage !== null) {
            $percentage *= 100;
        }
        $change = null;
        $average = null;
        if (($last !== null) && ($open !== null)) {
            $change = $last - $open;
            $average = $this->sum($open, $last) / 2;
        }
        $baseVolume = $this->safe_float($ticker, 'turnover_24h');
        $quoteVolume = $this->safe_float($ticker, 'volume_24h');
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'high_price_24h'),
            'low' => $this->safe_float($ticker, 'low_price_24h'),
            'bid' => $this->safe_float($ticker, 'bid_price'),
            'bidVolume' => null,
            'ask' => $this->safe_float($ticker, 'ask_price'),
            'askVolume' => null,
            'vwap' => $vwap,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->v2PublicGetTickers (array_merge($request, $params));
        //
        //     {
        //         ret_code => 0,
        //         ret_msg => 'OK',
        //         ext_code => '',
        //         ext_info => '',
        //         $result => array(
        //             {
        //                 $symbol => 'BTCUSD',
        //                 bid_price => '7680',
        //                 ask_price => '7680.5',
        //                 last_price => '7680.00',
        //                 last_tick_direction => 'MinusTick',
        //                 prev_price_24h => '7870.50',
        //                 price_24h_pcnt => '-0.024204',
        //                 high_price_24h => '8035.00',
        //                 low_price_24h => '7671.00',
        //                 prev_price_1h => '7780.00',
        //                 price_1h_pcnt => '-0.012853',
        //                 mark_price => '7683.27',
        //                 index_price => '7682.74',
        //                 open_interest => 188829147,
        //                 open_value => '23670.06',
        //                 total_turnover => '25744224.90',
        //                 turnover_24h => '102997.83',
        //                 total_volume => 225448878806,
        //                 volume_24h => 809919408,
        //                 funding_rate => '0.0001',
        //                 predicted_funding_rate => '0.0001',
        //                 next_funding_time => '2020-03-12T00:00:00Z',
        //                 countdown_hour => 7
        //             }
        //         ),
        //         time_now => '1583948195.818255'
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $first = $this->safe_value($result, 0);
        $timestamp = $this->safe_timestamp($response, 'time_now');
        $ticker = $this->parse_ticker($first, $market);
        $ticker['timestamp'] = $timestamp;
        $ticker['datetime'] = $this->iso8601($timestamp);
        return $ticker;
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        $this->load_markets();
        $response = $this->v2PublicGetTickers ($params);
        //
        //     {
        //         ret_code => 0,
        //         ret_msg => 'OK',
        //         ext_code => '',
        //         ext_info => '',
        //         $result => array(
        //             {
        //                 $symbol => 'BTCUSD',
        //                 bid_price => '7680',
        //                 ask_price => '7680.5',
        //                 last_price => '7680.00',
        //                 last_tick_direction => 'MinusTick',
        //                 prev_price_24h => '7870.50',
        //                 price_24h_pcnt => '-0.024204',
        //                 high_price_24h => '8035.00',
        //                 low_price_24h => '7671.00',
        //                 prev_price_1h => '7780.00',
        //                 price_1h_pcnt => '-0.012853',
        //                 mark_price => '7683.27',
        //                 index_price => '7682.74',
        //                 open_interest => 188829147,
        //                 open_value => '23670.06',
        //                 total_turnover => '25744224.90',
        //                 turnover_24h => '102997.83',
        //                 total_volume => 225448878806,
        //                 volume_24h => 809919408,
        //                 funding_rate => '0.0001',
        //                 predicted_funding_rate => '0.0001',
        //                 next_funding_time => '2020-03-12T00:00:00Z',
        //                 countdown_hour => 7
        //             }
        //         ),
        //         time_now => '1583948195.818255'
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $tickers = array();
        for ($i = 0; $i < count($result); $i++) {
            $ticker = $this->parse_ticker($result[$i]);
            $symbol = $ticker['symbol'];
            $tickers[$symbol] = $ticker;
        }
        return $this->filter_by_array($tickers, 'symbol', $symbols);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        // inverse perpetual BTC/USD
        //
        //     {
        //         symbol => 'BTCUSD',
        //         interval => '1',
        //         open_time => 1583952540,
        //         open => '7760.5',
        //         high => '7764',
        //         low => '7757',
        //         close => '7763.5',
        //         volume => '1259766',
        //         turnover => '162.32773718999994'
        //     }
        //
        // linear perpetual BTC/USDT
        //
        //     {
        //         "id":143536,
        //         "symbol":"BTCUSDT",
        //         "period":"15",
        //         "start_at":1587883500,
        //         "volume":1.035,
        //         "open":7540.5,
        //         "high":7541,
        //         "low":7540.5,
        //         "close":7541
        //     }
        //
        return array(
            $this->safe_timestamp_2($ohlcv, 'open_time', 'start_at'),
            $this->safe_float($ohlcv, 'open'),
            $this->safe_float($ohlcv, 'high'),
            $this->safe_float($ohlcv, 'low'),
            $this->safe_float($ohlcv, 'close'),
            $this->safe_float_2($ohlcv, 'turnover', 'volume'),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'interval' => $this->timeframes[$timeframe],
        );
        $duration = $this->parse_timeframe($timeframe);
        $now = $this->seconds();
        if ($since === null) {
            if ($limit === null) {
                throw new ArgumentsRequired($this->id . ' fetchOHLCV() requires a $since argument or a $limit argument');
            } else {
                $request['from'] = $now - $limit * $duration;
            }
        } else {
            $request['from'] = intval($since / 1000);
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // max 200, default 200
        }
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $method = ($marketType === 'linear') ? 'publicLinearGetKline' : 'v2PublicGetKlineList';
        $response = $this->$method (array_merge($request, $params));
        //
        // inverse perpetual BTC/USD
        //
        //     {
        //         ret_code => 0,
        //         ret_msg => 'OK',
        //         ext_code => '',
        //         ext_info => '',
        //         $result => array(
        //             array(
        //                 $symbol => 'BTCUSD',
        //                 interval => '1',
        //                 open_time => 1583952540,
        //                 open => '7760.5',
        //                 high => '7764',
        //                 low => '7757',
        //                 close => '7763.5',
        //                 volume => '1259766',
        //                 turnover => '162.32773718999994'
        //             ),
        //         ),
        //         time_now => '1583953082.397330'
        //     }
        //
        // linear perpetual BTC/USDT
        //
        //     {
        //         "ret_code":0,
        //         "ret_msg":"OK",
        //         "ext_code":"",
        //         "ext_info":"",
        //         "$result":array(
        //             {
        //                 "id":143536,
        //                 "$symbol":"BTCUSDT",
        //                 "period":"15",
        //                 "start_at":1587883500,
        //                 "volume":1.035,
        //                 "open":7540.5,
        //                 "high":7541,
        //                 "low":7540.5,
        //                 "close":7541
        //             }
        //         ),
        //         "time_now":"1587884120.168077"
        //     }
        //
        $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 => 43785688,
        //         $symbol => 'BTCUSD',
        //         $price => 7786,
        //         qty => 67,
        //         $side => 'Sell',
        //         time => '2020-03-11T19:18:30.123Z'
        //     }
        //
        // fetchMyTrades, fetchOrderTrades (private)
        //
        //     {
        //         "closed_size" => 0,
        //         "cross_seq" => 277136382,
        //         "exec_fee" => "0.0000001",
        //         "exec_id" => "256e5ef8-abfe-5772-971b-f944e15e0d68",
        //         "exec_price" => "8178.5",
        //         "exec_qty" => 1,
        //         // the docs say the exec_time field is "abandoned" now
        //         // the user should use "trade_time_ms"
        //         "exec_time" => "1571676941.70682",
        //         "exec_type" => "Trade", //Exec Type Enum
        //         "exec_value" => "0.00012227",
        //         "fee_rate" => "0.00075",
        //         "last_liquidity_ind" => "RemovedLiquidity", //Liquidity Enum
        //         "leaves_qty" => 0,
        //         "nth_fill" => 2,
        //         "order_id" => "7ad50cb1-9ad0-4f74-804b-d82a516e1029",
        //         "order_link_id" => "",
        //         "order_price" => "8178",
        //         "order_qty" => 1,
        //         "order_type" => "Market", //Order Type Enum
        //         "$side" => "Buy", //Side Enum
        //         "$symbol" => "BTCUSD", //Symbol Enum
        //         "user_id" => 1,
        //         "trade_time_ms" => 1577480599000
        //     }
        //
        $id = $this->safe_string_2($trade, 'id', 'exec_id');
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $amount = $this->safe_float_2($trade, 'qty', 'exec_qty');
        $cost = $this->safe_float($trade, 'exec_value');
        $price = $this->safe_float_2($trade, 'exec_price', 'price');
        if ($cost === null) {
            if ($amount !== null) {
                if ($price !== null) {
                    $cost = $amount * $price;
                }
            }
        }
        $timestamp = $this->parse8601($this->safe_string($trade, 'time'));
        if ($timestamp === null) {
            $timestamp = $this->safe_integer($trade, 'trade_time_ms');
        }
        $side = $this->safe_string_lower($trade, 'side');
        $lastLiquidityInd = $this->safe_string($trade, 'last_liquidity_ind');
        $takerOrMaker = ($lastLiquidityInd === 'AddedLiquidity') ? 'maker' : 'taker';
        $feeCost = $this->safe_float($trade, 'exec_fee');
        $fee = null;
        if ($feeCost !== null) {
            $feeCurrencyCode = $market['inverse'] ? $market['base'] : $market['quote'];
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
                'rate' => $this->safe_float($trade, 'fee_rate'),
            );
        }
        return array(
            'id' => $id,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => $this->safe_string($trade, 'order_id'),
            'type' => $this->safe_string_lower($trade, 'order_type'),
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'from' => 123, // from id
        );
        if ($limit !== null) {
            $request['count'] = $limit; // default 500, max 1000
        }
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $method = ($marketType === 'linear') ? 'publicLinearGetRecentTradingRecords' : 'v2PublicGetTradingRecords';
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         ret_code => 0,
        //         ret_msg => 'OK',
        //         ext_code => '',
        //         ext_info => '',
        //         $result => array(
        //             array(
        //                 id => 43785688,
        //                 $symbol => 'BTCUSD',
        //                 price => 7786,
        //                 qty => 67,
        //                 side => 'Sell',
        //                 time => '2020-03-11T19:18:30.123Z'
        //             ),
        //         ),
        //         time_now => '1583954313.393362'
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_trades($result, $market, $since, $limit);
    }

    public function parse_order_book($orderbook, $timestamp = null, $bidsKey = 'Buy', $asksKey = 'Sell', $priceKey = 'price', $amountKey = 'size') {
        $bids = array();
        $asks = array();
        for ($i = 0; $i < count($orderbook); $i++) {
            $bidask = $orderbook[$i];
            $side = $this->safe_string($bidask, 'side');
            if ($side === 'Buy') {
                $bids[] = $this->parse_bid_ask($bidask, $priceKey, $amountKey);
            } else if ($side === 'Sell') {
                $asks[] = $this->parse_bid_ask($bidask, $priceKey, $amountKey);
            } else {
                throw new ExchangeError($this->id . ' parseOrderBook encountered an unrecognized $bidask format => ' . $this->json($bidask));
            }
        }
        return array(
            'bids' => $this->sort_by($bids, 0, true),
            'asks' => $this->sort_by($asks, 0),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'nonce' => null,
        );
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->v2PublicGetOrderBookL2 (array_merge($request, $params));
        //
        //     {
        //         ret_code => 0,
        //         ret_msg => 'OK',
        //         ext_code => '',
        //         ext_info => '',
        //         $result => array(
        //             array( $symbol => 'BTCUSD', price => '7767.5', size => 677956, side => 'Buy' ),
        //             array( $symbol => 'BTCUSD', price => '7767', size => 580690, side => 'Buy' ),
        //             array( $symbol => 'BTCUSD', price => '7766.5', size => 475252, side => 'Buy' ),
        //             array( $symbol => 'BTCUSD', price => '7768', size => 330847, side => 'Sell' ),
        //             array( $symbol => 'BTCUSD', price => '7768.5', size => 97159, side => 'Sell' ),
        //             array( $symbol => 'BTCUSD', price => '7769', size => 6508, side => 'Sell' ),
        //         ),
        //         time_now => '1583954829.874823'
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $timestamp = $this->safe_timestamp($response, 'time_now');
        return $this->parse_order_book($result, $timestamp, 'Buy', 'Sell', 'price', 'size');
    }

    public function fetch_balance($params = array ()) {
        $this->load_markets();
        $request = array();
        $coin = $this->safe_string($params, 'coin');
        $code = $this->safe_string($params, 'code');
        if ($coin !== null) {
            $request['coin'] = $coin;
        } else if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        $response = $this->v2PrivateGetWalletBalance (array_merge($request, $params));
        //
        //     {
        //         ret_code => 0,
        //         ret_msg => 'OK',
        //         ext_code => '',
        //         ext_info => '',
        //         $result => {
        //             BTC => array(
        //                 equity => 0,
        //                 available_balance => 0,
        //                 used_margin => 0,
        //                 order_margin => 0,
        //                 position_margin => 0,
        //                 occ_closing_fee => 0,
        //                 occ_funding_fee => 0,
        //                 wallet_balance => 0,
        //                 realised_pnl => 0,
        //                 unrealised_pnl => 0,
        //                 cum_realised_pnl => 0,
        //                 given_cash => 0,
        //                 service_cash => 0
        //             }
        //         ),
        //         time_now => '1583937810.370020',
        //         rate_limit_status => 119,
        //         rate_limit_reset_ms => 1583937810367,
        //         rate_limit => 120
        //     }
        //
        $result = array(
            'info' => $response,
        );
        $balances = $this->safe_value($response, 'result', array());
        $currencyIds = is_array($balances) ? array_keys($balances) : array();
        for ($i = 0; $i < count($currencyIds); $i++) {
            $currencyId = $currencyIds[$i];
            $balance = $balances[$currencyId];
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_float($balance, 'available_balance');
            $account['used'] = $this->safe_float($balance, 'used_margin');
            $account['total'] = $this->safe_float($balance, 'equity');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function parse_order_status($status) {
        $statuses = array(
            // basic orders
            'Created' => 'open',
            'Rejected' => 'rejected', // order is triggered but failed upon being placed
            'New' => 'open',
            'PartiallyFilled' => 'open',
            'Filled' => 'closed',
            'Cancelled' => 'canceled',
            'PendingCancel' => 'canceling', // the engine has received the cancellation but there is no guarantee that it will be successful
            // conditional orders
            'Active' => 'open', // order is triggered and placed successfully
            'Untriggered' => 'open', // order waits to be triggered
            'Triggered' => 'closed', // order is triggered
            // 'Cancelled' => 'canceled', // order is cancelled
            // 'Rejected' => 'rejected', // order is triggered but fail to be placed
            'Deactivated' => 'canceled', // conditional order was cancelled before triggering
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_time_in_force($timeInForce) {
        $timeInForces = array(
            'GoodTillCancel' => 'GTC',
            'ImmediateOrCancel' => 'IOC',
            'FillOrKill' => 'FOK',
            'PostOnly' => 'PO',
        );
        return $this->safe_string($timeInForces, $timeInForce, $timeInForce);
    }

    public function parse_order($order, $market = null) {
        //
        // createOrder
        //
        //     {
        //         "user_id" => 1,
        //         "order_id" => "335fd977-e5a5-4781-b6d0-c772d5bfb95b",
        //         "$symbol" => "BTCUSD",
        //         "$side" => "Buy",
        //         "order_type" => "Limit",
        //         "$price" => 8800,
        //         "qty" => 1,
        //         "time_in_force" => "GoodTillCancel",
        //         "order_status" => "Created",
        //         "last_exec_time" => 0,
        //         "last_exec_price" => 0,
        //         "leaves_qty" => 1,
        //         "cum_exec_qty" => 0, // in contracts, where 1 contract = 1 quote currency unit (USD for inverse contracts)
        //         "cum_exec_value" => 0, // in contract's underlying currency (BTC for inverse contracts)
        //         "cum_exec_fee" => 0,
        //         "reject_reason" => "",
        //         "order_link_id" => "",
        //         "created_at" => "2019-11-30T11:03:43.452Z",
        //         "updated_at" => "2019-11-30T11:03:43.455Z"
        //     }
        //
        // fetchOrder
        //
        //     {
        //         "user_id" : 599946,
        //         "$symbol" : "BTCUSD",
        //         "$side" : "Buy",
        //         "order_type" : "Limit",
        //         "$price" : "7948",
        //         "qty" : 10,
        //         "time_in_force" : "GoodTillCancel",
        //         "order_status" : "Filled",
        //         "ext_fields" : array(
        //             "o_req_num" : -1600687220498,
        //             "xreq_type" : "x_create"
        //         ),
        //         "last_exec_time" : "1588150113.968422",
        //         "last_exec_price" : "7948",
        //         "leaves_qty" : 0,
        //         "leaves_value" : "0",
        //         "cum_exec_qty" : 10,
        //         "cum_exec_value" : "0.00125817",
        //         "cum_exec_fee" : "-0.00000031",
        //         "reject_reason" : "",
        //         "cancel_type" : "",
        //         "order_link_id" : "",
        //         "created_at" : "2020-04-29T08:45:24.399146Z",
        //         "updated_at" : "2020-04-29T08:48:33.968422Z",
        //         "order_id" : "dd2504b9-0157-406a-99e1-efa522373944"
        //     }
        //
        // conditional $order
        //
        //     {
        //         "user_id":##,
        //         "$symbol":"BTCUSD",
        //         "$side":"Buy",
        //         "order_type":"Market",
        //         "$price":0,
        //         "qty":10,
        //         "time_in_force":"GoodTillCancel",
        //         "stop_order_type":"Stop",
        //         "trigger_by":"LastPrice",
        //         "base_price":11833,
        //         "order_status":"Untriggered",
        //         "ext_fields":array(
        //             "stop_order_type":"Stop",
        //             "trigger_by":"LastPrice",
        //             "base_price":11833,
        //             "expected_direction":"Rising",
        //             "trigger_price":12400,
        //             "close_on_trigger":true,
        //             "op_from":"api",
        //             "remark":"145.53.159.48",
        //             "o_req_num":0
        //         ),
        //         "leaves_qty":10,
        //         "leaves_value":0.00080645,
        //         "reject_reason":null,
        //         "cross_seq":-1,
        //         "created_at":"2020-08-21T09:18:48.000Z",
        //         "updated_at":"2020-08-21T09:18:48.000Z",
        //         "stop_px":12400,
        //         "stop_order_id":"3f3b54b1-3379-42c7-8510-44f4d9915be0"
        //     }
        //
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = null;
        $base = null;
        $timestamp = $this->parse8601($this->safe_string($order, 'created_at'));
        $id = $this->safe_string_2($order, 'order_id', 'stop_order_id');
        $type = $this->safe_string_lower($order, 'order_type');
        $price = $this->safe_float($order, 'price');
        if ($price === 0.0) {
            $price = null;
        }
        $average = $this->safe_float($order, 'average_price');
        $amount = $this->safe_float($order, 'qty');
        $cost = $this->safe_float($order, 'cum_exec_value');
        $filled = $this->safe_float($order, 'cum_exec_qty');
        $remaining = $this->safe_float($order, 'leaves_qty');
        if ($market !== null) {
            $symbol = $market['symbol'];
            $base = $market['base'];
        }
        $lastTradeTimestamp = $this->safe_timestamp($order, 'last_exec_time');
        if ($lastTradeTimestamp === 0) {
            $lastTradeTimestamp = null;
        }
        if (($filled === null) && ($amount !== null) && ($remaining !== null)) {
            $filled = $amount - $remaining;
        }
        if ($filled !== null) {
            if (($remaining === null) && ($amount !== null)) {
                $remaining = $amount - $filled;
            }
            if ($cost === null) {
                if ($price !== null) {
                    $cost = $price * $filled;
                }
            }
            if (($type === 'market') && ($cost !== null) && ($cost > 0)) {
                $price = null;
                if ($average === null) {
                    $average = $filled / $cost;
                }
            }
        }
        $status = $this->parse_order_status($this->safe_string_2($order, 'order_status', 'stop_order_status'));
        $side = $this->safe_string_lower($order, 'side');
        $feeCost = $this->safe_float($order, 'cum_exec_fee');
        $fee = null;
        if ($feeCost !== null) {
            $feeCost = abs($feeCost);
            $fee = array(
                'cost' => $feeCost,
                'currency' => $base,
            );
        }
        $clientOrderId = $this->safe_string($order, 'order_link_id');
        if (($clientOrderId !== null) && (strlen($clientOrderId) < 1)) {
            $clientOrderId = null;
        }
        $timeInForce = $this->parse_time_in_force($this->safe_string($order, 'time_in_force'));
        $stopPrice = $this->safe_float($order, 'stop_px');
        $postOnly = ($timeInForce === 'PO');
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        );
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'order_link_id' => 'string', // one of order_id, stop_order_id or order_link_id is required
            // regular orders ---------------------------------------------
            // 'order_id' => $id, // one of order_id or order_link_id is required for regular orders
            // conditional orders ---------------------------------------------
            // 'stop_order_id' => $id, // one of stop_order_id or order_link_id is required for conditional orders
        );
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $method = ($marketType === 'linear') ? 'privateLinearGetOrderSearch' : 'v2PrivateGetOrder';
        $stopOrderId = $this->safe_string($params, 'stop_order_id');
        if ($stopOrderId === null) {
            $orderLinkId = $this->safe_string($params, 'order_link_id');
            if ($orderLinkId === null) {
                $request['order_id'] = $id;
            }
        } else {
            $method = ($marketType === 'linear') ? 'privateLinearGetStopOrderSearch' : 'v2PrivateGetStopOrder';
        }
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "OK",
        //         "ext_code" => "",
        //         "ext_info" => "",
        //         "$result" => array(
        //             "user_id" => 1,
        //             "$symbol" => "BTCUSD",
        //             "side" => "Sell",
        //             "order_type" => "Limit",
        //             "price" => "8083",
        //             "qty" => 10,
        //             "time_in_force" => "GoodTillCancel",
        //             "order_status" => "New",
        //             "ext_fields" => array( "o_req_num" => -308787, "xreq_type" => "x_create", "xreq_offset" => 4154640 ),
        //             "leaves_qty" => 10,
        //             "leaves_value" => "0.00123716",
        //             "cum_exec_qty" => 0,
        //             "reject_reason" => "",
        //             "order_link_id" => "",
        //             "created_at" => "2019-10-21T07:28:19.396246Z",
        //             "updated_at" => "2019-10-21T07:28:19.396246Z",
        //             "order_id" => "efa44157-c355-4a98-b6d6-1d846a936b93"
        //         ),
        //         "time_now" => "1571651135.291930",
        //         "rate_limit_status" => 99, // The remaining number of accesses in one minute
        //         "rate_limit_reset_ms" => 1580885703683,
        //         "rate_limit" => 100
        //     }
        //
        // conditional orders
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "OK",
        //         "ext_code" => "",
        //         "ext_info" => "",
        //         "$result" => array(
        //             "user_id" => 1,
        //             "$symbol" => "BTCUSD",
        //             "side" => "Buy",
        //             "order_type" => "Limit",
        //             "price" => "8000",
        //             "qty" => 1,
        //             "time_in_force" => "GoodTillCancel",
        //             "order_status" => "Untriggered",
        //             "ext_fields" => array(),
        //             "leaves_qty" => 1,
        //             "leaves_value" => "0.00013333",
        //             "cum_exec_qty" => 0,
        //             "cum_exec_value" => null,
        //             "cum_exec_fee" => null,
        //             "reject_reason" => "",
        //             "order_link_id" => "",
        //             "created_at" => "2019-12-27T19:56:24.052194Z",
        //             "updated_at" => "2019-12-27T19:56:24.052194Z",
        //             "order_id" => "378a1bbc-a93a-4e75-87f4-502ea754ba36"
        //         ),
        //         "time_now" => "1577476584.386958",
        //         "rate_limit_status" => 99,
        //         "rate_limit_reset_ms" => 1580885703683,
        //         "rate_limit" => 100
        //     }
        //
        $result = $this->safe_value($response, 'result');
        return $this->parse_order($result, $market);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $qty = $this->amount_to_precision($symbol, $amount);
        if ($market['inverse']) {
            $qty = intval($qty);
        } else {
            $qty = floatval($qty);
        }
        $request = array(
            // orders ---------------------------------------------------------
            'side' => $this->capitalize($side),
            'symbol' => $market['id'],
            'order_type' => $this->capitalize($type),
            'qty' => $qty, // order quantity in USD, integer only
            // 'price' => floatval($this->price_to_precision($symbol, $price)), // required for limit orders
            'time_in_force' => 'GoodTillCancel', // ImmediateOrCancel, FillOrKill, PostOnly
            // 'take_profit' => 123.45, // take profit $price, only take effect upon opening the position
            // 'stop_loss' => 123.45, // stop loss $price, only take effect upon opening the position
            // 'reduce_only' => false, // reduce only, required for linear orders
            // when creating a closing order, bybit recommends a True value for
            // close_on_trigger to avoid failing due to insufficient available margin
            // 'close_on_trigger' => false, required for linear orders
            // 'order_link_id' => 'string', // unique client order id, max 36 characters
            // conditional orders ---------------------------------------------
            // base_price is used to compare with the value of stop_px, to decide
            // whether your conditional order will be triggered by crossing trigger
            // $price from upper $side or lower $side, mainly used to identify the
            // expected direction of the current conditional order
            // 'base_price' => 123.45, // required for conditional orders
            // 'stop_px' => 123.45, // trigger $price, required for conditional orders
            // 'trigger_by' => 'LastPrice', // IndexPrice, MarkPrice
        );
        $priceIsRequired = false;
        if ($type === 'limit') {
            $priceIsRequired = true;
        }
        if ($priceIsRequired) {
            if ($price !== null) {
                $request['price'] = floatval($this->price_to_precision($symbol, $price));
            } else {
                throw new ArgumentsRequired($this->id . ' createOrder() requires a $price argument for a ' . $type . ' order');
            }
        }
        $clientOrderId = $this->safe_string_2($params, 'order_link_id', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['order_link_id'] = $clientOrderId;
            $params = $this->omit($params, array( 'order_link_id', 'clientOrderId' ));
        }
        $stopPx = $this->safe_value_2($params, 'stop_px', 'stopPrice');
        $basePrice = $this->safe_value($params, 'base_price');
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $method = ($marketType === 'linear') ? 'privateLinearPostOrderCreate' : 'v2PrivatePostOrderCreate';
        if ($marketType === 'linear') {
            $method = 'privateLinearPostOrderCreate';
            $request['reduce_only'] = false;
            $request['close_on_trigger'] = false;
        }
        if ($stopPx !== null) {
            if ($basePrice === null) {
                throw new ArgumentsRequired($this->id . ' createOrder() requires both the stop_px and base_price $params for a conditional ' . $type . ' order');
            } else {
                $method = ($marketType === 'linear') ? 'privateLinearPostStopOrderCreate' : 'v2PrivatePostStopOrderCreate';
                $request['stop_px'] = floatval($this->price_to_precision($symbol, $stopPx));
                $request['base_price'] = floatval($this->price_to_precision($symbol, $basePrice));
                $params = $this->omit($params, array( 'stop_px', 'stopPrice', 'base_price' ));
            }
        } else if ($basePrice !== null) {
            throw new ArgumentsRequired($this->id . ' createOrder() requires both the stop_px and base_price $params for a conditional ' . $type . ' order');
        }
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "OK",
        //         "ext_code" => "",
        //         "ext_info" => "",
        //         "$result" => array(
        //             "user_id" => 1,
        //             "order_id" => "335fd977-e5a5-4781-b6d0-c772d5bfb95b",
        //             "$symbol" => "BTCUSD",
        //             "$side" => "Buy",
        //             "order_type" => "Limit",
        //             "$price" => 8800,
        //             "$qty" => 1,
        //             "time_in_force" => "GoodTillCancel",
        //             "order_status" => "Created",
        //             "last_exec_time" => 0,
        //             "last_exec_price" => 0,
        //             "leaves_qty" => 1,
        //             "cum_exec_qty" => 0,
        //             "cum_exec_value" => 0,
        //             "cum_exec_fee" => 0,
        //             "reject_reason" => "",
        //             "order_link_id" => "",
        //             "created_at" => "2019-11-30T11:03:43.452Z",
        //             "updated_at" => "2019-11-30T11:03:43.455Z"
        //         ),
        //         "time_now" => "1575111823.458705",
        //         "rate_limit_status" => 98,
        //         "rate_limit_reset_ms" => 1580885703683,
        //         "rate_limit" => 100
        //     }
        //
        // conditional orders
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => array(
        //             "user_id" => 1,
        //             "$symbol" => "BTCUSD",
        //             "$side" => "Buy",
        //             "order_type" => "Limit",
        //             "$price" => 8000,
        //             "$qty" => 1,
        //             "time_in_force" => "GoodTillCancel",
        //             "stop_order_type" => "Stop",
        //             "trigger_by" => "LastPrice",
        //             "base_price" => 7000,
        //             "order_status" => "Untriggered",
        //             "ext_fields" => array(
        //                 "stop_order_type" => "Stop",
        //                 "trigger_by" => "LastPrice",
        //                 "base_price" => 7000,
        //                 "expected_direction" => "Rising",
        //                 "trigger_price" => 7500,
        //                 "op_from" => "api",
        //                 "remark" => "127.0.01",
        //                 "o_req_num" => 0
        //             ),
        //             "leaves_qty" => 1,
        //             "leaves_value" => 0.00013333,
        //             "reject_reason" => null,
        //             "cross_seq" => -1,
        //             "created_at" => "2019-12-27T12:48:24.000Z",
        //             "updated_at" => "2019-12-27T12:48:24.000Z",
        //             "stop_px" => 7500,
        //             "stop_order_id" => "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5"
        //         ),
        //         "ext_info" => null,
        //         "time_now" => "1577450904.327654",
        //         "rate_limit_status" => 99,
        //         "rate_limit_reset_ms" => 1577450904335,
        //         "rate_limit" => "100"
        //     }
        //
        $result = $this->safe_value($response, 'result');
        return $this->parse_order($result, $market);
    }

    public function edit_order($id, $symbol, $type, $side, $amount = null, $price = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' editOrder() requires an $symbol argument');
        }
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            // 'order_id' => $id, // only for non-conditional orders
            'symbol' => $market['id'],
            // 'p_r_qty' => $this->amount_to_precision($symbol, $amount), // new order quantity, optional
            // 'p_r_price' $this->priceToprecision ($symbol, $price), // new order $price, optional
            // ----------------------------------------------------------------
            // conditional orders
            // 'stop_order_id' => $id, // only for conditional orders
            // 'p_r_trigger_price' => 123.45, // new trigger $price also known as stop_px
        );
        $method = ($marketType === 'linear') ? 'privateLinearPostOrderReplace' : 'v2PrivatePostOrderReplace';
        $stopOrderId = $this->safe_string($params, 'stop_order_id');
        if ($stopOrderId !== null) {
            $method = ($marketType === 'linear') ? 'privateLinearPostStopOrderReplace' : 'v2PrivatePostStopOrderReplace';
            $request['stop_order_id'] = $stopOrderId;
            $params = $this->omit($params, array( 'stop_order_id' ));
        } else {
            $request['order_id'] = $id;
        }
        if ($amount !== null) {
            $qty = $this->amount_to_precision($symbol, $amount);
            if ($market['inverse']) {
                $qty = intval($qty);
            } else {
                $qty = floatval($qty);
            }
            $request['p_r_qty'] = $qty;
        }
        if ($price !== null) {
            $request['p_r_price'] = floatval($this->price_to_precision($symbol, $price));
        }
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => array( "order_id" => "efa44157-c355-4a98-b6d6-1d846a936b93" ),
        //         "time_now" => "1539778407.210858",
        //         "rate_limit_status" => 99, // remaining number of accesses in one minute
        //         "rate_limit_reset_ms" => 1580885703683,
        //         "rate_limit" => 100
        //     }
        //
        // conditional orders
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => array( "stop_order_id" => "378a1bbc-a93a-4e75-87f4-502ea754ba36" ),
        //         "ext_info" => null,
        //         "time_now" => "1577475760.604942",
        //         "rate_limit_status" => 96,
        //         "rate_limit_reset_ms" => 1577475760612,
        //         "rate_limit" => "100"
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return array(
            'info' => $response,
            'id' => $this->safe_string_2($result, 'order_id', 'stop_order_id'),
            'order_id' => $this->safe_string($result, 'order_id'),
            'stop_order_id' => $this->safe_string($result, 'stop_order_id'),
        );
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'order_link_id' => 'string', // one of order_id, stop_order_id or order_link_id is required
            // regular orders ---------------------------------------------
            // 'order_id' => $id, // one of order_id or order_link_id is required for regular orders
            // conditional orders ---------------------------------------------
            // 'stop_order_id' => $id, // one of stop_order_id or order_link_id is required for conditional orders
        );
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_value($marketTypes, $symbol);
        $method = ($marketType === 'linear') ? 'privateLinearPostOrderCancel' : 'v2PrivatePostOrderCancel';
        $stopOrderId = $this->safe_string($params, 'stop_order_id');
        if ($stopOrderId === null) {
            $orderLinkId = $this->safe_string($params, 'order_link_id');
            if ($orderLinkId === null) {
                $request['order_id'] = $id;
            }
        } else {
            $method = ($marketType === 'linear') ? 'privateLinearPostStopOrderCancel' : 'v2PrivatePostStopOrderCancel';
        }
        $response = $this->$method (array_merge($request, $params));
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_order($result, $market);
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $options = $this->safe_value($this->options, 'cancelAllOrders', array());
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $defaultMethod = ($marketType === 'linear') ? 'privateLinearPostOrderCancelAll' : 'v2PrivatePostOrderCancelAll';
        $method = $this->safe_string($options, 'method', $defaultMethod);
        $response = $this->$method (array_merge($request, $params));
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_orders($result, $market);
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'order_id' => 'string'
            // 'order_link_id' => 'string', // unique client order id, max 36 characters
            // 'symbol' => $market['id'], // default BTCUSD
            // 'order' => 'desc', // asc
            // 'page' => 1,
            // 'limit' => 20, // max 50
            // 'order_status' => 'Created,New'
            // conditional orders ---------------------------------------------
            // 'stop_order_id' => 'string',
            // 'stop_order_status' => 'Untriggered',
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $options = $this->safe_value($this->options, 'fetchOrders', array());
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $defaultMethod = ($marketType === 'linear') ? 'privateLinearGetOrderList' : 'v2PrivateGetOrderList';
        $query = $params;
        if ((is_array($params) && array_key_exists('stop_order_id', $params)) || (is_array($params) && array_key_exists('stop_order_status', $params))) {
            $stopOrderStatus = $this->safe_value($params, 'stopOrderStatus');
            if ($stopOrderStatus !== null) {
                if (gettype($stopOrderStatus) === 'array' && count(array_filter(array_keys($stopOrderStatus), 'is_string')) == 0) {
                    $stopOrderStatus = implode(',', $stopOrderStatus);
                }
                $request['stop_order_status'] = $stopOrderStatus;
                $query = $this->omit($params, 'stop_order_status');
            }
            $defaultMethod = ($marketType === 'linear') ? 'privateLinearGetStopOrderList' : 'v2PrivateGetStopOrderList';
        }
        $method = $this->safe_string($options, 'method', $defaultMethod);
        $response = $this->$method (array_merge($request, $query));
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => {
        //             "current_page" => 1,
        //             "last_page" => 6,
        //             "$data" => array(
        //                 array(
        //                     "user_id" => 1,
        //                     "$symbol" => "BTCUSD",
        //                     "side" => "Sell",
        //                     "order_type" => "Market",
        //                     "price" => 7074,
        //                     "qty" => 2,
        //                     "time_in_force" => "ImmediateOrCancel",
        //                     "order_status" => "Filled",
        //                     "ext_fields" => array(
        //                         "close_on_trigger" => true,
        //                         "orig_order_type" => "BLimit",
        //                         "prior_x_req_price" => 5898.5,
        //                         "op_from" => "pc",
        //                         "remark" => "127.0.0.1",
        //                         "o_req_num" => -34799032763,
        //                         "xreq_type" => "x_create"
        //                     ),
        //                     "last_exec_time" => "1577448481.696421",
        //                     "last_exec_price" => 7070.5,
        //                     "leaves_qty" => 0,
        //                     "leaves_value" => 0,
        //                     "cum_exec_qty" => 2,
        //                     "cum_exec_value" => 0.00028283,
        //                     "cum_exec_fee" => 0.00002,
        //                     "reject_reason" => "NoError",
        //                     "order_link_id" => "",
        //                     "created_at" => "2019-12-27T12:08:01.000Z",
        //                     "updated_at" => "2019-12-27T12:08:01.000Z",
        //                     "order_id" => "f185806b-b801-40ff-adec-52289370ed62"
        //                 }
        //             )
        //         ),
        //         "ext_info" => null,
        //         "time_now" => "1577448922.437871",
        //         "rate_limit_status" => 98,
        //         "rate_limit_reset_ms" => 1580885703683,
        //         "rate_limit" => 100
        //     }
        //
        // conditional orders
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => array(
        //             "current_page" => 1,
        //             "last_page" => 1,
        //             "$data" => array(
        //                 array(
        //                     "user_id" => 1,
        //                     "stop_order_status" => "Untriggered",
        //                     "$symbol" => "BTCUSD",
        //                     "side" => "Buy",
        //                     "order_type" => "Limit",
        //                     "price" => 8000,
        //                     "qty" => 1,
        //                     "time_in_force" => "GoodTillCancel",
        //                     "stop_order_type" => "Stop",
        //                     "trigger_by" => "LastPrice",
        //                     "base_price" => 7000,
        //                     "order_link_id" => "",
        //                     "created_at" => "2019-12-27T12:48:24.000Z",
        //                     "updated_at" => "2019-12-27T12:48:24.000Z",
        //                     "stop_px" => 7500,
        //                     "stop_order_id" => "a85cd1c0-a9a4-49d3-a1bd-bab5ebe946d5"
        //                 ),
        //             )
        //         ),
        //         "ext_info" => null,
        //         "time_now" => "1577451658.755468",
        //         "rate_limit_status" => 599,
        //         "rate_limit_reset_ms" => 1577451658762,
        //         "rate_limit" => 600
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $data = $this->safe_value($result, 'data', array());
        return $this->parse_orders($data, $market, $since, $limit);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $defaultStatuses = array(
            'Rejected',
            'Filled',
            'Cancelled',
            // conditional orders
            // 'Active',
            // 'Triggered',
            // 'Cancelled',
            // 'Rejected',
            // 'Deactivated',
        );
        $options = $this->safe_value($this->options, 'fetchClosedOrders', array());
        $status = $this->safe_value($options, 'order_status', $defaultStatuses);
        if (gettype($status) === 'array' && count(array_filter(array_keys($status), 'is_string')) == 0) {
            $status = implode(',', $status);
        }
        $request = array();
        $stopOrderStatus = $this->safe_value($params, 'stop_order_status');
        if ($stopOrderStatus === null) {
            $request['order_status'] = $status;
        } else {
            $request['stop_order_status'] = $stopOrderStatus;
        }
        return $this->fetch_orders($symbol, $since, $limit, array_merge($request, $params));
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $defaultStatuses = array(
            'Created',
            'New',
            'PartiallyFilled',
            'PendingCancel',
            // conditional orders
            // 'Untriggered',
        );
        $options = $this->safe_value($this->options, 'fetchOpenOrders', array());
        $status = $this->safe_value($options, 'order_status', $defaultStatuses);
        if (gettype($status) === 'array' && count(array_filter(array_keys($status), 'is_string')) == 0) {
            $status = implode(',', $status);
        }
        $request = array();
        $stopOrderStatus = $this->safe_value($params, 'stop_order_status');
        if ($stopOrderStatus === null) {
            $request['order_status'] = $status;
        } else {
            $request['stop_order_status'] = $stopOrderStatus;
        }
        return $this->fetch_orders($symbol, $since, $limit, array_merge($request, $params));
    }

    public function fetch_order_trades($id, $symbol = null, $since = null, $limit = null, $params = array ()) {
        $request = array(
            'order_id' => $id,
        );
        return $this->fetch_my_trades($symbol, $since, $limit, array_merge($request, $params));
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'order_id' => 'f185806b-b801-40ff-adec-52289370ed62', // if not provided will return user's trading records
            // 'symbol' => $market['id'],
            // 'start_time' => intval($since / 1000),
            // 'page' => 1,
            // 'limit' 20, // max 50
        );
        $market = null;
        if ($symbol === null) {
            $orderId = $this->safe_string($params, 'order_id');
            if ($orderId === null) {
                throw new ArgumentsRequired($this->id . ' fetchMyTrades() requires a $symbol argument or an order_id param');
            } else {
                $request['order_id'] = $orderId;
                $params = $this->omit($params, 'order_id');
            }
        } else {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['start_time'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 20, max 50
        }
        $marketTypes = $this->safe_value($this->options, 'marketTypes', array());
        $marketType = $this->safe_string($marketTypes, $symbol);
        $method = ($marketType === 'linear') ? 'privateLinearGetTradeExecutionList' : 'v2PrivateGetExecutionList';
        $response = $this->$method (array_merge($request, $params));
        //
        // inverse
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "OK",
        //         "ext_code" => "",
        //         "ext_info" => "",
        //         "$result" => {
        //             "order_id" => "Abandoned!!", // Abandoned!!
        //             "trade_list" => array(
        //                 array(
        //                     "closed_size" => 0,
        //                     "cross_seq" => 277136382,
        //                     "exec_fee" => "0.0000001",
        //                     "exec_id" => "256e5ef8-abfe-5772-971b-f944e15e0d68",
        //                     "exec_price" => "8178.5",
        //                     "exec_qty" => 1,
        //                     "exec_time" => "1571676941.70682",
        //                     "exec_type" => "Trade", //Exec Type Enum
        //                     "exec_value" => "0.00012227",
        //                     "fee_rate" => "0.00075",
        //                     "last_liquidity_ind" => "RemovedLiquidity", //Liquidity Enum
        //                     "leaves_qty" => 0,
        //                     "nth_fill" => 2,
        //                     "order_id" => "7ad50cb1-9ad0-4f74-804b-d82a516e1029",
        //                     "order_link_id" => "",
        //                     "order_price" => "8178",
        //                     "order_qty" => 1,
        //                     "order_type" => "Market", //Order Type Enum
        //                     "side" => "Buy", //Side Enum
        //                     "$symbol" => "BTCUSD", //Symbol Enum
        //                     "user_id" => 1
        //                 }
        //             )
        //         ),
        //         "time_now" => "1577483699.281488",
        //         "rate_limit_status" => 118,
        //         "rate_limit_reset_ms" => 1577483699244737,
        //         "rate_limit" => 120
        //     }
        //
        // linear
        //
        //     {
        //         "ret_code":0,
        //         "ret_msg":"OK",
        //         "ext_code":"",
        //         "ext_info":"",
        //         "$result":{
        //             "current_page":1,
        //             "data":array(
        //                 array(
        //                     "order_id":"b59418ec-14d4-4ef9-b9f4-721d5d576974",
        //                     "order_link_id":"",
        //                     "side":"Sell",
        //                     "$symbol":"BTCUSDT",
        //                     "exec_id":"0327284d-faec-5191-bd89-acc5b4fafda9",
        //                     "price":0.5,
        //                     "order_price":0.5,
        //                     "order_qty":0.01,
        //                     "order_type":"Market",
        //                     "fee_rate":0.00075,
        //                     "exec_price":9709.5,
        //                     "exec_type":"Trade",
        //                     "exec_qty":0.01,
        //                     "exec_fee":0.07282125,
        //                     "exec_value":97.095,
        //                     "leaves_qty":0,
        //                     "closed_size":0.01,
        //                     "last_liquidity_ind":"RemovedLiquidity",
        //                     "trade_time":1591648052,
        //                     "trade_time_ms":1591648052861
        //                 }
        //             )
        //         ),
        //         "time_now":"1591736501.979264",
        //         "rate_limit_status":119,
        //         "rate_limit_reset_ms":1591736501974,
        //         "rate_limit":120
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $trades = $this->safe_value_2($result, 'trade_list', 'data', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'coin' => $currency['id'],
            // 'currency' => $currency['id'], // alias
            // 'start_date' => $this->iso8601($since),
            // 'end_date' => $this->iso8601(till),
            'wallet_fund_type' => 'Deposit', // Deposit, Withdraw, RealisedPNL, Commission, Refund, Prize, ExchangeOrderWithdraw, ExchangeOrderDeposit
            // 'page' => 1,
            // 'limit' => 20, // max 50
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['start_date'] = $this->ymd($since);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->v2PrivateGetWalletFundRecords (array_merge($request, $params));
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => {
        //             "$data" => array(
        //                 array(
        //                     "id" => 234467,
        //                     "user_id" => 1,
        //                     "coin" => "BTC",
        //                     "wallet_id" => 27913,
        //                     "type" => "Realized P&L",
        //                     "amount" => "-0.00000006",
        //                     "tx_id" => "",
        //                     "address" => "BTCUSD",
        //                     "wallet_balance" => "0.03000330",
        //                     "exec_time" => "2019-12-09T00:00:25.000Z",
        //                     "cross_seq" => 0
        //                 }
        //             )
        //         ),
        //         "ext_info" => null,
        //         "time_now" => "1577481867.115552",
        //         "rate_limit_status" => 119,
        //         "rate_limit_reset_ms" => 1577481867122,
        //         "rate_limit" => 120
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $data = $this->safe_value($result, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'coin' => $currency['id'],
            // 'start_date' => $this->iso8601($since),
            // 'end_date' => $this->iso8601(till),
            // 'status' => 'Pending', // ToBeConfirmed, UnderReview, Pending, Success, CancelByUser, Reject, Expire
            // 'page' => 1,
            // 'limit' => 20, // max 50
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['start_date'] = $this->ymd($since);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->v2PrivateGetWalletWithdrawList (array_merge($request, $params));
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => array(
        //             "$data" => array(
        //                 array(
        //                     "id" => 137,
        //                     "user_id" => 1,
        //                     "coin" => "XRP", // Coin Enum
        //                     "status" => "Pending", // Withdraw Status Enum
        //                     "amount" => "20.00000000",
        //                     "fee" => "0.25000000",
        //                     "address" => "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM",
        //                     "tx_id" => "",
        //                     "submited_at" => "2019-06-11T02:20:24.000Z",
        //                     "updated_at" => "2019-06-11T02:20:24.000Z"
        //                 ),
        //             ),
        //             "current_page" => 1,
        //             "last_page" => 1
        //         ),
        //         "ext_info" => null,
        //         "time_now" => "1577482295.125488",
        //         "rate_limit_status" => 119,
        //         "rate_limit_reset_ms" => 1577482295132,
        //         "rate_limit" => 120
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $data = $this->safe_value($result, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit, $params);
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            'ToBeConfirmed' => 'pending',
            'UnderReview' => 'pending',
            'Pending' => 'pending',
            'Success' => 'ok',
            'CancelByUser' => 'canceled',
            'Reject' => 'rejected',
            'Expire' => 'expired',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // fetchWithdrawals
        //
        //     {
        //         "id" => 137,
        //         "user_id" => 1,
        //         "coin" => "XRP", // Coin Enum
        //         "$status" => "Pending", // Withdraw Status Enum
        //         "amount" => "20.00000000",
        //         "$fee" => "0.25000000",
        //         "$address" => "rH7H595XYEVTEHU2FySYsWnmfACBnZS9zM",
        //         "tx_id" => "",
        //         "submited_at" => "2019-06-11T02:20:24.000Z",
        //         "updated_at" => "2019-06-11T02:20:24.000Z"
        //     }
        //
        // fetchDeposits ledger entries
        //
        //     {
        //         "id" => 234467,
        //         "user_id" => 1,
        //         "coin" => "BTC",
        //         "wallet_id" => 27913,
        //         "type" => "Realized P&L",
        //         "amount" => "-0.00000006",
        //         "tx_id" => "",
        //         "$address" => "BTCUSD",
        //         "wallet_balance" => "0.03000330",
        //         "exec_time" => "2019-12-09T00:00:25.000Z",
        //         "cross_seq" => 0
        //     }
        //
        $currencyId = $this->safe_string($transaction, 'coin');
        $code = $this->safe_currency_code($currencyId, $currency);
        $timestamp = $this->parse8601($this->safe_string_2($transaction, 'submited_at', 'exec_time'));
        $updated = $this->parse8601($this->safe_string($transaction, 'updated_at'));
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $address = $this->safe_string($transaction, 'address');
        $feeCost = $this->safe_float($transaction, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'id'),
            'txid' => $this->safe_string($transaction, 'tx_id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'address' => $address,
            'addressTo' => null,
            'addressFrom' => null,
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => 'withdrawal',
            'amount' => $this->safe_float($transaction, 'amount'),
            'currency' => $code,
            'status' => $status,
            'updated' => $updated,
            'fee' => $fee,
        );
    }

    public function fetch_ledger($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'coin' => $currency['id'],
            // 'currency' => $currency['id'], // alias
            // 'start_date' => $this->iso8601($since),
            // 'end_date' => $this->iso8601(till),
            // 'wallet_fund_type' => 'Deposit', // Withdraw, RealisedPNL, Commission, Refund, Prize, ExchangeOrderWithdraw, ExchangeOrderDeposit
            // 'page' => 1,
            // 'limit' => 20, // max 50
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['start_date'] = $this->ymd($since);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->v2PrivateGetWalletFundRecords (array_merge($request, $params));
        //
        //     {
        //         "ret_code" => 0,
        //         "ret_msg" => "ok",
        //         "ext_code" => "",
        //         "$result" => {
        //             "$data" => array(
        //                 array(
        //                     "id" => 234467,
        //                     "user_id" => 1,
        //                     "coin" => "BTC",
        //                     "wallet_id" => 27913,
        //                     "type" => "Realized P&L",
        //                     "amount" => "-0.00000006",
        //                     "tx_id" => "",
        //                     "address" => "BTCUSD",
        //                     "wallet_balance" => "0.03000330",
        //                     "exec_time" => "2019-12-09T00:00:25.000Z",
        //                     "cross_seq" => 0
        //                 }
        //             )
        //         ),
        //         "ext_info" => null,
        //         "time_now" => "1577481867.115552",
        //         "rate_limit_status" => 119,
        //         "rate_limit_reset_ms" => 1577481867122,
        //         "rate_limit" => 120
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $data = $this->safe_value($result, 'data', array());
        return $this->parse_ledger($data, $currency, $since, $limit);
    }

    public function parse_ledger_entry($item, $currency = null) {
        //
        //     {
        //         "$id" => 234467,
        //         "user_id" => 1,
        //         "coin" => "BTC",
        //         "wallet_id" => 27913,
        //         "$type" => "Realized P&L",
        //         "$amount" => "-0.00000006",
        //         "tx_id" => "",
        //         "address" => "BTCUSD",
        //         "wallet_balance" => "0.03000330",
        //         "exec_time" => "2019-12-09T00:00:25.000Z",
        //         "cross_seq" => 0
        //     }
        //
        $currencyId = $this->safe_string($item, 'coin');
        $code = $this->safe_currency_code($currencyId, $currency);
        $amount = $this->safe_float($item, 'amount');
        $after = $this->safe_float($item, 'wallet_balance');
        $direction = ($amount < 0) ? 'out' : 'in';
        $before = null;
        if ($after !== null && $amount !== null) {
            $difference = ($direction === 'out') ? $amount : -$amount;
            $before = $this->sum($after, $difference);
        }
        $timestamp = $this->parse8601($this->safe_string($item, 'exec_time'));
        $type = $this->parse_ledger_entry_type($this->safe_string($item, 'type'));
        $id = $this->safe_string($item, 'id');
        $referenceId = $this->safe_string($item, 'tx_id');
        return array(
            'id' => $id,
            'currency' => $code,
            'account' => $this->safe_string($item, 'wallet_id'),
            'referenceAccount' => null,
            'referenceId' => $referenceId,
            'status' => null,
            'amount' => $amount,
            'before' => $before,
            'after' => $after,
            'fee' => null,
            'direction' => $direction,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'type' => $type,
            'info' => $item,
        );
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'Deposit' => 'transaction',
            'Withdraw' => 'transaction',
            'RealisedPNL' => 'trade',
            'Commission' => 'fee',
            'Refund' => 'cashback',
            'Prize' => 'prize', // ?
            'ExchangeOrderWithdraw' => 'transaction',
            'ExchangeOrderDeposit' => 'transaction',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->implode_params($this->urls['api'], array( 'hostname' => $this->hostname ));
        $type = $this->safe_string($api, 0);
        $section = $this->safe_string($api, 1);
        $request = '/' . $type . '/' . $section . '/' . $path;
        // public v2
        if ($section === 'public') {
            if ($params) {
                $request .= '?' . $this->rawencode($params);
            }
        } else if ($type === 'public') {
            if ($params) {
                $request .= '?' . $this->rawencode($params);
            }
        } else {
            $this->check_required_credentials();
            $timestamp = $this->nonce();
            $query = array_merge($params, array(
                'api_key' => $this->apiKey,
                'recv_window' => $this->options['recvWindow'],
                'timestamp' => $timestamp,
            ));
            $auth = $this->rawencode($this->keysort($query));
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret));
            if ($method === 'POST') {
                $body = $this->json(array_merge($query, array(
                    'sign' => $signature,
                )));
                $headers = array(
                    'Content-Type' => 'application/json',
                );
            } else {
                $request .= '?' . $auth . '&sign=' . $signature;
            }
        }
        $url .= $request;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors($httpCode, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if (!$response) {
            return; // fallback to default error handler
        }
        //
        //     {
        //         ret_code => 10001,
        //         ret_msg => 'ReadMapCB => expect { or n, but found \u0000, error ' +
        //         'found in #0 byte of ...||..., bigger context ' +
        //         '...||...',
        //         ext_code => '',
        //         ext_info => '',
        //         result => null,
        //         time_now => '1583934106.590436'
        //     }
        //
        $errorCode = $this->safe_value($response, 'ret_code');
        if ($errorCode !== 0) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
            throw new ExchangeError($feedback); // unknown message
        }
    }
}
