<?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;
use \ccxt\InvalidOrder;
use \ccxt\NotSupported;

class hbtc extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'hbtc',
            'name' => 'HBTC',
            'countries' => array( 'CN' ),
            'rateLimit' => 2000,
            'version' => 'v1',
            'has' => array(
                'cancelOrder' => true,
                'CORS' => false,
                'createOrder' => true,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchBidAsk' => true,
                'fetchBidsAsks' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => false,
                'fetchDepositAddress' => false,
                'fetchDeposits' => true,
                'fetchLedger' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingLimits' => true,
                'fetchWithdrawals' => true,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '4h' => '4h',
                '6h' => '6h',
                '8h' => '8h',
                '12h' => '12h',
                '1d' => '1d',
                '3d' => '3d',
                '1w' => '1w',
                '1M' => '1M',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/51840849/80134449-70663300-85a7-11ea-8942-e204cdeaab5d.jpg', // 交易所LOGO
                'api' => array(
                    'quote' => 'https://api.hbtc.com/openapi/quote', // 市场API数据端点
                    'contract' => 'https://api.hbtc.com/openapi/contract', // 合约API数据端点
                    'option' => 'https://api.hbtc.com/openapi/option', // 合约API数据端点
                    'public' => 'https://api.hbtc.com/openapi', // 公共API数据端点
                    'private' => 'https://api.hbtc.com/openapi', // 私有API数据端点
                    'zendesk' => 'https://hbtc.zendesk.com/hc/en-us',
                ),
                'www' => 'https://www.hbtc.com', // 公司主页
                'referral' => 'https://www.hbtc.com/register/O2S8NS', // 邀请链接
                'doc' => 'https://github.com/bhexopen/BHEX-OpenApi/tree/master/doc', // openapi文档地址
                'fees' => 'https://hbtc.zendesk.com/hc/zh-cn/articles/360009274694', // 费率介绍
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'ping',
                        'time',
                        'brokerInfo', // 查询当前broker交易规则和symbol信息
                        'getOptions',
                    ),
                ),
                'quote' => array(
                    'get' => array(
                        'depth', // 获取深度
                        'depth/merged',
                        'trades', // 获取当前最新成交
                        'klines', // 获取K线数据
                        'ticker/24hr', // 获取24小时价格变化数据
                        'ticker/price',
                        'ticker/bookTicker',
                        'contract/index', // 获取合约标的指数价格
                        'contract/depth', // 获取合约深度
                        'contract/depth/merged',
                        'contract/trades', // 获取合约最近成交,
                        'contract/klines', // 获取合约的K线数据
                        'contract/ticker/24hr',
                        'option/index',
                        'option/depth',
                        'option/depth/merged',
                        'option/trades',
                        'option/klines',
                        'option/ticker/24hr',
                    ),
                ),
                'contract' => array(
                    'get' => array(
                        // public
                        'insurance',
                        'fundingRate', // 获取资金费率信息
                        // private
                        'openOrders', // 查询合约当前委托
                        'historyOrders', // 查询合约历史委托
                        'getOrder', // 查询合约订单详情
                        'myTrades', // 查询合约历史成交
                        'positions', // 查询合约当前持仓
                        'account', // 查询合约账户信息
                    ),
                    'post' => array(
                        'order', // 创建合约订单
                        'modifyMargin', // 修改保证金
                    ),
                    'delete' => array(
                        'order/cancel', // 取消合约订单
                        'order/batchCancel',
                    ),
                ),
                'option' => array(
                    'get' => array(
                        'openOrders',
                        'positions',
                        'historyOrders',
                        // 'getOrder',
                        'myTrades',
                        'settlements',
                        'account',
                    ),
                    'post' => array(
                        'order',
                    ),
                    'delete' => array(
                        'order/cancel',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'order', // 查询订单
                        'openOrders', // 查询当前委托
                        'historyOrders', // 查询历史委托
                        'account', // 获取当前账户信息
                        'myTrades', // 查询历史成交
                        'depositOrders',
                        'withdrawalOrders',
                        'withdraw/detail',
                        'balance_flow',
                    ),
                    'post' => array(
                        'order', // 创建新订单
                        'order/test',
                        'userDataStream',
                        'subAccount/query',
                        'transfer',
                        'user/transfer',
                        'withdraw',
                    ),
                    'put' => array(
                        'userDataStream',
                    ),
                    'delete' => array(
                        'order', // 取消订单
                        'userDataStream',
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'maker' => 0.001,
                    'taker' => 0.001,
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    // general server or network errors
                    '-1000' => '\\ccxt\\ExchangeError', // An unknown error occured while processing the request
                    '-1001' => '\\ccxt\\ExchangeError', // Internal error, unable to process your request. Please try again
                    '-1002' => '\\ccxt\\AuthenticationError', // You are not authorized to execute this request. Request need API Key included in. We suggest that API Key be included in any request
                    '-1003' => '\\ccxt\\RateLimitExceeded', // Too many requests, please use the websocket for live updates
                    '-1004' => '\\ccxt\\BadRequest',
                    '-1005' => '\\ccxt\\PermissionDenied',
                    '-1006' => '\\ccxt\\BadResponse', // An unexpected response was received from the message bus. Execution status unknown. OPEN API server find some exception in execute request.Please report to Customer service
                    '-1007' => '\\ccxt\\RequestTimeout', // Timeout waiting for response from backend server. Send status unknown, execution status unknown
                    '-1014' => '\\ccxt\\InvalidOrder', // Unsupported order combination
                    '-1015' => '\\ccxt\\RateLimitExceeded', // Reach the rate limit.Please slow down your request speed
                    '-1016' => '\\ccxt\\ExchangeNotAvailable', // This service is no longer available
                    '-1020' => '\\ccxt\\NotSupported', // This operation is not supported
                    '-1021' => '\\ccxt\\BadRequest', // Timestamp for this request is outside of the recvWindow
                    '-1022' => '\\ccxt\\AuthenticationError', // Signature for this request is not valid
                    // request issues
                    '-1100' => '\\ccxt\\BadRequest', // Illegal characters found in a parameter
                    '-1101' => '\\ccxt\\BadRequest', // Too many parameters sent for this endpoint
                    '-1102' => '\\ccxt\\BadRequest', // A mandatory parameter was not sent, was empty/null, or malformed
                    '-1103' => '\\ccxt\\BadRequest', // An unknown parameter was sent
                    '-1104' => '\\ccxt\\BadRequest', // Not all sent parameters were read
                    '-1105' => '\\ccxt\\BadRequest', // A parameter was empty
                    '-1106' => '\\ccxt\\BadRequest', // A parameter was sent when not required
                    '-1111' => '\\ccxt\\BadRequest', // Precision is over the maximum defined for this asset
                    '-1112' => '\\ccxt\\NullResponse', // No orders on book for symbol
                    '-1114' => '\\ccxt\\InvalidOrder', // TimeInForce parameter sent when not required
                    '-1115' => '\\ccxt\\InvalidOrder', // Invalid timeInForce
                    '-1116' => '\\ccxt\\InvalidOrder', // Invalid orderType
                    '-1117' => '\\ccxt\\InvalidOrder', // Invalid side
                    '-1118' => '\\ccxt\\InvalidOrder', // New client order ID was empty
                    '-1119' => '\\ccxt\\InvalidOrder', // Original client order ID was empty
                    '-1120' => '\\ccxt\\BadRequest', // Invalid interval
                    '-1121' => '\\ccxt\\BadSymbol', // Invalid symbol
                    '-1125' => '\\ccxt\\AuthenticationError', // This listenKey does not exist
                    '-1127' => '\\ccxt\\BadRequest', // Lookup interval is too big
                    '-1128' => '\\ccxt\\BadRequest', // Combination of optional parameters invalid
                    '-1130' => '\\ccxt\\BadRequest', // Invalid data sent for a parameter
                    '-1131' => '\\ccxt\\InsufficientFunds',
                    '-1132' => '\\ccxt\\InvalidOrder', // Order price too high
                    '-1133' => '\\ccxt\\InvalidOrder', // Order price lower than the minimum,please check general broker info
                    '-1134' => '\\ccxt\\InvalidOrder', // Order price decimal too long,please check general broker info
                    '-1135' => '\\ccxt\\InvalidOrder', // Order quantity too large
                    '-1136' => '\\ccxt\\InvalidOrder', // Order quantity lower than the minimum
                    '-1137' => '\\ccxt\\InvalidOrder', // Order quantity decimal too long
                    '-1138' => '\\ccxt\\InvalidOrder', // Order price exceeds permissible range
                    '-1139' => '\\ccxt\\InvalidOrder', // Order has been filled
                    '-1140' => '\\ccxt\\InvalidOrder', // Transaction amount lower than the minimum
                    '-1141' => '\\ccxt\\InvalidOrder', // Duplicate clientOrderId
                    '-1142' => '\\ccxt\\InvalidOrder', // Order has been canceled
                    '-1143' => '\\ccxt\\OrderNotFound', // Cannot be found on order book
                    '-1144' => '\\ccxt\\InvalidOrder', // Order has been locked
                    '-1145' => '\\ccxt\\InvalidOrder', // This order type does not support cancellation
                    '-1146' => '\\ccxt\\RequestTimeout', // Order creation timeout
                    '-1147' => '\\ccxt\\RequestTimeout', // Order cancellation timeout
                    '-1149' => '\\ccxt\\InvalidOrder', // Create order failed
                    '-1187' => '\\ccxt\\InvalidAddress', // Withdrawal address not in whitelist
                    '-2010' => '\\ccxt\\InvalidOrder', // NEW_ORDER_REJECTED
                    '-2011' => '\\ccxt\\InvalidOrder', // CANCEL_REJECTED
                    '-2013' => '\\ccxt\\OrderNotFound', // Order does not exist
                    '-2014' => '\\ccxt\\AuthenticationError', // API-key format invalid
                    '-2015' => '\\ccxt\\AuthenticationError', // Invalid API-key, IP, or permissions for action
                    '-2016' => '\\ccxt\\ExchangeError', // No trading window could be found for the symbol. Try ticker/24hrs instead
                ),
            ),
            // exchange-specific options
            'options' => array(
                'fetchTickers' => array(
                    'method' => 'quoteGetTicker24hr',
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()) {
        $response = $this->publicGetTime ($params);
        //
        //     {
        //         "serverTime" => 1527777538000
        //     }
        //
        return $this->safe_integer($response, 'serverTime');
    }

    public function parse_market($market, $type = 'spot') {
        $filters = $this->safe_value($market, 'filters', array());
        $id = $this->safe_string($market, 'symbol');
        $baseId = $this->safe_string($market, 'baseAsset');
        $quoteId = $this->safe_string($market, 'quoteAsset');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $spot = true;
        $future = false;
        $option = false;
        $inverse = false;
        if ($type === 'future') {
            $symbol = $id;
            $spot = false;
            $future = true;
            $inverse = $this->safe_value($market, 'inverse', false);
            $baseId = $this->safe_string($market, 'underlying');
            $base = $this->safe_currency_code($baseId);
        } else if ($type === 'option') {
            $symbol = $id;
            $spot = false;
            $option = true;
        }
        $amountMin = null;
        $amountMax = null;
        $priceMin = null;
        $priceMax = null;
        $costMin = null;
        for ($j = 0; $j < count($filters); $j++) {
            $filter = $filters[$j];
            $filterType = $this->safe_string($filter, 'filterType');
            if ($filterType === 'LOT_SIZE') {
                $amountMin = $this->safe_float($filter, 'minQty');
                $amountMax = $this->safe_float($filter, 'maxQty');
            }
            if ($filterType === 'PRICE_FILTER') {
                $priceMin = $this->safe_float($filter, 'minPrice');
                $priceMax = $this->safe_float($filter, 'maxPrice');
            }
            if ($filterType === 'MIN_NOTIONAL') {
                $costMin = $this->safe_float($filter, 'minNotional');
            }
        }
        if (($costMin === null) && ($amountMin !== null) && ($priceMin !== null)) {
            $costMin = $amountMin * $priceMin;
        }
        $precision = array(
            'price' => $this->safe_float_2($market, 'quotePrecision', 'quoteAssetPrecision'),
            'amount' => $this->safe_float($market, 'baseAssetPrecision'),
        );
        $limits = array(
            'amount' => array(
                'min' => $amountMin,
                'max' => $amountMax,
            ),
            'price' => array(
                'min' => $priceMin,
                'max' => $priceMax,
            ),
            'cost' => array(
                'min' => $costMin,
                'max' => null,
            ),
        );
        return array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'active' => true,
            'type' => $type,
            'spot' => $spot,
            'future' => $future,
            'option' => $option,
            'inverse' => $inverse,
            'precision' => $precision,
            'limits' => $limits,
            'info' => $market,
        );
    }

    public function fetch_markets($params = array ()) {
        $response = $this->publicGetBrokerInfo ($params);
        //
        //     {
        //         "timezone":"UTC",
        //         "serverTime":"1588015885118",
        //         "brokerFilters":array(),
        //         "$symbols":array(
        //             array(
        //                 "filters":array(
        //                     array("minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"),
        //                     array("minQty":"0.0005","maxQty":"100000.00000000","stepSize":"0.000001","filterType":"LOT_SIZE"),
        //                     array("minNotional":"5","filterType":"MIN_NOTIONAL")
        //                 ),
        //                 "exchangeId":"301",
        //                 "symbol":"BTCUSDT",
        //                 "symbolName":"BTCUSDT",
        //                 "status":"TRADING",
        //                 "baseAsset":"BTC",
        //                 "baseAssetPrecision":"0.000001",
        //                 "quoteAsset":"USDT",
        //                 "quotePrecision":"0.01",
        //                 "icebergAllowed":false
        //             ),
        //         ),
        //         "$options":array(
        //             array(
        //                 "filters":array(
        //                     array("minPrice":"0.01","maxPrice":"100000.00000000","tickSize":"0.01","filterType":"PRICE_FILTER"),
        //                     array("minQty":"0.01","maxQty":"100000.00000000","stepSize":"0.001","filterType":"LOT_SIZE"),
        //                     array("minNotional":"1","filterType":"MIN_NOTIONAL")
        //                 ),
        //                 "exchangeId":"301",
        //                 "symbol":"BTC0501CS8500",
        //                 "symbolName":"BTC0501CS8500",
        //                 "status":"TRADING",
        //                 "baseAsset":"BTC0501CS8500",
        //                 "baseAssetPrecision":"0.001",
        //                 "quoteAsset":"BUSDT",
        //                 "quotePrecision":"0.01",
        //                 "icebergAllowed":false
        //             ),
        //         ),
        //         "$contracts":array(
        //             array(
        //                 "filters":array(
        //                     array("minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"),
        //                     array("minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"),
        //                     array("minNotional":"0.000001","filterType":"MIN_NOTIONAL")
        //                 ),
        //                 "exchangeId":"301",
        //                 "symbol":"BTC-PERP-REV",
        //                 "symbolName":"BTC-PERP-REV",
        //                 "status":"TRADING",
        //                 "baseAsset":"BTC-PERP-REV",
        //                 "baseAssetPrecision":"1",
        //                 "quoteAsset":"USDT",
        //                 "quoteAssetPrecision":"0.1",
        //                 "icebergAllowed":false,
        //                 "inverse":true,
        //                 "index":"BTCUSDT",
        //                 "marginToken":"TBTC",
        //                 "marginPrecision":"0.00000001",
        //                 "contractMultiplier":"1.0",
        //                 "underlying":"TBTC",
        //                 "riskLimits":array(
        //                     array("riskLimitId":"200000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"),
        //                     array("riskLimitId":"200000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"),
        //                     array("riskLimitId":"200000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"),
        //                     array("riskLimitId":"200000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02")
        //                 )
        //             ),
        //             array(
        //                 "filters":array(
        //                     array("minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"),
        //                     array("minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"),
        //                     array("minNotional":"0.000001","filterType":"MIN_NOTIONAL")
        //                 ),
        //                 "exchangeId":"301",
        //                 "symbol":"BTC-SWAP",
        //                 "symbolName":"BTC-SWAP",
        //                 "status":"TRADING",
        //                 "baseAsset":"BTC-SWAP",
        //                 "baseAssetPrecision":"1",
        //                 "quoteAsset":"USDT",
        //                 "quoteAssetPrecision":"0.1",
        //                 "icebergAllowed":false,
        //                 "inverse":true,
        //                 "index":"BTCUSDT",
        //                 "marginToken":"BTC",
        //                 "marginPrecision":"0.00000001",
        //                 "contractMultiplier":"1.0",
        //                 "underlying":"BTC",
        //                 "riskLimits":array(
        //                     array("riskLimitId":"500000001","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"),
        //                     array("riskLimitId":"500000002","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"),
        //                     array("riskLimitId":"500000003","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"),
        //                     array("riskLimitId":"500000004","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02")
        //                 )
        //             ),
        //             array(
        //                 "filters":array(
        //                     array("minPrice":"0.1","maxPrice":"100000.00000000","tickSize":"0.1","filterType":"PRICE_FILTER"),
        //                     array("minQty":"1","maxQty":"100000.00000000","stepSize":"1","filterType":"LOT_SIZE"),
        //                     array("minNotional":"0.000000001","filterType":"MIN_NOTIONAL")
        //                 ),
        //                 "exchangeId":"301",
        //                 "symbol":"BTC-PERP-BUSDT",
        //                 "symbolName":"BTC-PERP-BUSDT",
        //                 "status":"TRADING",
        //                 "baseAsset":"BTC-PERP-BUSDT",
        //                 "baseAssetPrecision":"1",
        //                 "quoteAsset":"BUSDT",
        //                 "quoteAssetPrecision":"0.1",
        //                 "icebergAllowed":false,
        //                 "inverse":false,
        //                 "index":"BTCUSDT",
        //                 "marginToken":"BUSDT",
        //                 "marginPrecision":"0.0001",
        //                 "contractMultiplier":"0.0001",
        //                 "underlying":"TBTC",
        //                 "riskLimits":array(
        //                     array("riskLimitId":"600000132","quantity":"1000000.0","initialMargin":"0.01","maintMargin":"0.005"),
        //                     array("riskLimitId":"600000133","quantity":"2000000.0","initialMargin":"0.02","maintMargin":"0.01"),
        //                     array("riskLimitId":"600000134","quantity":"3000000.0","initialMargin":"0.03","maintMargin":"0.015"),
        //                     array("riskLimitId":"600000135","quantity":"4000000.0","initialMargin":"0.04","maintMargin":"0.02")
        //                 )
        //             ),
        //         )
        //     }
        //
        $result = array();
        $symbols = $this->safe_value($response, 'symbols', array());
        for ($i = 0; $i < count($symbols); $i++) {
            $market = $this->parse_market($symbols[$i], 'spot');
            $result[] = $market;
        }
        $options = $this->safe_value($response, 'options', array());
        for ($i = 0; $i < count($options); $i++) {
            $market = $this->parse_market($options[$i], 'option');
            $result[] = $market;
        }
        $contracts = $this->safe_value($response, 'contracts', array());
        for ($i = 0; $i < count($contracts); $i++) {
            $market = $this->parse_market($contracts[$i], 'future');
            $result[] = $market;
        }
        return $result;
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit; // default 40, max 40
        }
        $response = $this->quoteGetDepth (array_merge($request, $params));
        //
        //     {
        //         "time":1588068913453,
        //         "bids":[
        //             ["0.025278","0.0202"],
        //             ["0.025277","16.1132"],
        //             ["0.025276","7.9056"],
        //         ]
        //         "asks":[
        //             ["0.025302","5.9999"],
        //             ["0.025303","34.9151"],
        //             ["0.025304","92.391"],
        //         ]
        //     }
        //
        $timestamp = $this->safe_integer($response, 'time');
        return $this->parse_order_book($response, $timestamp);
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->quoteGetTicker24hr (array_merge($request, $params));
        //
        //     {
        //         "time":1588069860794,
        //         "$symbol":"BNB0501PS16",
        //         "bestBidPrice":"0.2129",
        //         "bestAskPrice":"0.3163",
        //         "volume":"33547",
        //         "quoteVolume":"10801.987",
        //         "lastPrice":"0.2625",
        //         "highPrice":"0.3918",
        //         "lowPrice":"0.2625",
        //         "openPrice":"0.362",
        //     }
        //
        return $this->parse_ticker($response, $market);
    }

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

    public function fetch_bid_ask($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->quoteGetTickerBookTicker (array_merge($request, $params));
        //
        //     {
        //         "$symbol" => "LTCBTC",
        //         "bidPrice" => "4.00000000",
        //         "bidQty" => "431.00000000",
        //         "askPrice" => "4.00000200",
        //         "askQty" => "9.00000000"
        //     }
        //
        return $this->parse_ticker($response, $market);
    }

    public function fetch_bids_asks($symbols = null, $params = array ()) {
        $this->load_markets();
        $response = $this->quoteGetTickerBookTicker ($params);
        //
        //     array(
        //         array(
        //             "symbol" => "LTCBTC",
        //             "bidPrice" => "4.00000000",
        //             "bidQty" => "431.00000000",
        //             "askPrice" => "4.00000200",
        //             "askQty" => "9.00000000"
        //         ),
        //         array(
        //             "symbol" => "ETHBTC",
        //             "bidPrice" => "0.07946700",
        //             "bidQty" => "9.00000000",
        //             "askPrice" => "100000.00000000",
        //             "askQty" => "1000.00000000"
        //         ),
        //     )
        //
        return $this->parse_tickers($response, $symbols);
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        $this->load_markets();
        $options = $this->safe_value($this->options, 'fetchTickers', array());
        $defaultMethod = $this->safe_string($options, 'method', 'quoteGetTicker24hr');
        $defaultType = $this->safe_string($options, 'type', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $query = $this->omit($params, 'type');
        $method = $defaultMethod;
        if ($type === 'future') {
            $method = 'quoteGetContractTicker24hr';
        } else if ($type === 'option') {
            $method = 'quoteGetOptionTicker24hr';
        }
        $response = $this->$method ($query);
        //
        //     array(
        //         array(
        //             "time" => 1538725500422,
        //             "symbol" => "ETHBTC",
        //             "lastPrice" => "4.00000200",
        //             "openPrice" => "99.00000000",
        //             "highPrice" => "100.00000000",
        //             "lowPrice" => "0.10000000",
        //             "volume" => "8913.30000000"
        //         ),
        //     )
        //
        return $this->parse_tickers($response, $symbols);
    }

    public function fetch_balance($params = array ()) {
        $this->load_markets();
        $options = $this->safe_value($this->options, 'fetchBalance', array());
        $defaultType = $this->safe_string($options, 'type', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $query = $this->omit($params, 'type');
        $method = 'privateGetAccount';
        if ($type === 'future') {
            $method = 'contractGetAccount';
        } else if ($type === 'option') {
            $method = 'optionGetAccount';
        }
        $response = $this->$method ($query);
        //
        // spot
        //
        //     {
        //         'balances' => array(
        //             array(
        //                 'asset' => 'ALGO',
        //                 'free' => '0',
        //                 'locked' => '0'
        //             ),
        //             {
        //                 'asset' => 'BHT',
        //                 'free' => '0',
        //                 'locked' => '0'
        //             }
        //         )
        //     }
        //
        // contract
        //
        //     {
        //         "BUSDT":array(
        //             "total":"1000",
        //             "availableMargin":"1000",
        //             "positionMargin":"0",
        //             "orderMargin":"0",
        //             "tokenId":"BUSDT"
        //         ),
        //         "TBTC":{
        //             "total":"0.5",
        //             "availableMargin":"0.5",
        //             "positionMargin":"0",
        //             "orderMargin":"0",
        //             "tokenId":"TBTC"
        //         }
        //     }
        //
        // option
        //
        //     {
        //         "optionAsset":"",
        //         "$balances":array(
        //             array(
        //                 "tokenName":"USDT",
        //                 "free":"0.0",
        //                 "locked":"0.0",
        //                 "margin":"0.0"
        //             ),
        //             {
        //                 "tokenName":"BUSDT",
        //                 "free":"0.0",
        //                 "locked":"0.0",
        //                 "margin":"0.0"
        //             }
        //         )
        //     }
        //
        $balances = $this->safe_value($response, 'balances');
        $result = array( 'info' => $response );
        if ($balances !== null) {
            for ($i = 0; $i < count($balances); $i++) {
                $balance = $balances[$i];
                $currencyId = $this->safe_string_2($balance, 'asset', 'tokenName');
                $code = $this->safe_currency_code($currencyId);
                $account = $this->account();
                $account['free'] = $this->safe_float($balance, 'free');
                $account['used'] = $this->safe_float($balance, 'locked');
                $result[$code] = $account;
            }
        } else {
            $currencyIds = is_array($response) ? array_keys($response) : array();
            for ($i = 0; $i < count($currencyIds); $i++) {
                $currencyId = $currencyIds[$i];
                $code = $this->safe_currency_code($currencyId);
                $balance = $response[$currencyId];
                $account = $this->account();
                $account['free'] = $this->safe_float($balance, 'availableMargin');
                $account['total'] = $this->safe_float($balance, 'total');
                $result[$code] = $account;
            }
        }
        return $this->parse_balance($result);
    }

    public function fetch_trades($symbol, $since = null, $limit = 50, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit; // default 500, max 1000
        }
        $response = $this->quoteGetTrades (array_merge($request, $params));
        //
        //     array(
        //         array("price":"0.025344","time":1588084082060,"qty":"1","isBuyerMaker":false),
        //         array("price":"0.02535","time":1588084086021,"qty":"0.553","isBuyerMaker":true),
        //         array("price":"0.025348","time":1588084097037,"qty":"1","isBuyerMaker":false),
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     array(
        //         1587906000000, // open time
        //         "0.1761", // open
        //         "0.1761", // high
        //         "0.1761", // low
        //         "0.1761", // close
        //         "0", // base volume
        //         0, // close time
        //         "0", // quote volume
        //         0, // number of trades
        //         "0", // taker buy base asset volume
        //         "0" // taker buy quote asset volume
        //     )
        //
        return array(
            $this->safe_integer($ohlcv, 0),
            $this->safe_float($ohlcv, 1),
            $this->safe_float($ohlcv, 2),
            $this->safe_float($ohlcv, 3),
            $this->safe_float($ohlcv, 4),
            $this->safe_float($ohlcv, 5),
        );
    }

    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],
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 500, max 500
        }
        $response = $this->quoteGetKlines (array_merge($request, $params));
        //
        //     [
        //         [1587906000000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"],
        //         [1587906180000,"0.1761","0.1761","0.1761","0.1761","0",0,"0",0,"0","0"],
        //         [1587906360000,"0.1761","0.1848","0.1761","0.1848","53",0,"9.7944",1,"0","0"],
        //     ]
        //
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = null;
        $request = array(
            // if only fromId is set，it will get orders < that fromId in descending order
            // if only toId is set, it will get orders > that toId in ascending order
            // if fromId is set and toId is set, it will get orders < that fromId and > that toId in descending order
            // if fromId is not set and toId it not set, most recent order are returned in descending order
            // 'fromId' => '43287482374',
            // 'toId' => '43287482374',
            // 'endTime' => $this->milliseconds(), // optional, spot only
        );
        $defaultType = $this->safe_string($this->options, 'type', 'spot');
        $options = $this->safe_value($this->options, 'fetchMyTrades', array());
        $fetchMyTradesType = $this->safe_string($options, 'type', $defaultType);
        $type = $this->safe_string($params, 'type', $fetchMyTradesType);
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            $type = $market['type'];
        }
        $query = $this->omit($params, 'type');
        if ($limit !== null) {
            // spot default 500, max 1000
            // futures and $options default 20, max 1000
            $request['limit'] = $limit;
        }
        $method = 'privateGetMyTrades';
        if ($type === 'future') {
            $method = 'contractGetMyTrades';
        } else {
            if ($type === 'option') {
                $method = 'optionGetMyTrades';
            } else {
                if ($symbol === null) {
                    throw new ArgumentsRequired($this->id . ' fetchMyTrades() requires a `$symbol` argument for ' . $type . ' markets');
                }
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
                // spot only?
                if ($since !== null) {
                    $request['startTime'] = $since;
                }
            }
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $response = $this->$method (array_merge($request, $query));
        //
        // spot
        //
        //     array(
        //         {
        //             "id":"616384027512920576",
        //             "$symbol":"TBTCBUSDT",
        //             "orderId":"616384027202542080",
        //             "matchOrderId":"605124954767266560",
        //             "price":"6826.06",
        //             "qty":"0.1",
        //             "commission":"0.682606",
        //             "commissionAsset":"BUSDT",
        //             "time":"1588214701982",
        //             "isBuyer":false,
        //             "isMaker":false,
        //             "fee":{
        //                 "feeTokenId":"BUSDT",
        //                 "feeTokenName":"BUSDT",
        //                 "fee":"0.682606"
        //             }
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $orderSide = strtoupper($side);
        $orderType = strtoupper($type);
        $request = array(
            'symbol' => $market['id'],
            // BUY or SELL for spot and options
            'side' => $orderSide,
            // GTC, FOK, IOC for spot and options
            // GTC, FOK, IOC, LIMIT_MAKER for futures
            // 'timeInForce' => 'GTC',
        );
        $query = $params;
        $method = 'privatePostOrder';
        if ($market['type'] === 'future') {
            if (($orderSide !== 'BUY_OPEN') && ($orderSide !== 'SELL_OPEN') && ($orderSide !== 'BUY_CLOSE') && ($orderSide !== 'SELL_CLOSE')) {
                throw new NotSupported($this->id . ' createOrder() does not support order $side ' . $side . ' for ' . $market['type'] . ' markets, only BUY_OPEN, SELL_OPEN, BUY_CLOSE and SELL_CLOSE are supported');
            }
            if (($orderType !== 'LIMIT') && ($orderType !== 'STOP')) {
                throw new NotSupported($this->id . ' createOrder() does not support order $type ' . $type . ' for ' . $market['type'] . ' markets, only LIMIT and STOP are supported');
            }
            $clientOrderId = $this->safe_value($params, 'clientOrderId');
            if ($clientOrderId === null) {
                throw new ArgumentsRequired($this->id . ' createOrder() requires a $clientOrderId parameter for ' . $market['type'] . ' markets, supply $clientOrderId in the $params argument');
            }
            $leverage = $this->safe_value($params, 'leverage');
            if ($leverage === null && ($orderSide === 'BUY_OPEN' || $orderSide === 'SELL_OPEN')) {
                throw new NotSupported($this->id . ' createOrder() requires a $leverage parameter for ' . $market['type'] . ' markets if $orderSide is BUY_OPEN or SELL_OPEN');
            }
            $method = 'contractPostOrder';
            $priceType = $this->safe_string($params, 'priceType');
            if ($priceType === null) {
                $request['price'] = $this->price_to_precision($symbol, $price);
            } else {
                $request['priceType'] = $priceType;
                if ($priceType === 'INPUT') {
                    $request['price'] = $this->price_to_precision($symbol, $price);
                }
            }
            $request['orderType'] = strtoupper($type); // LIMIT, STOP
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
            // $request['leverage'] = 1; // not required for closing orders
            $request['leverage'] = $leverage;
            $request['clientOrderId'] = $clientOrderId;
            // optional
            // $request['priceType'] = 'INPUT', // INPUT, OPPONENT, QUEUE, OVER, MARKET
            // $request['triggerPrice'] = 123.45;
        } else {
            if ($market['type'] === 'option') {
                $method = 'optionPostOrder';
            }
            $newClientOrderId = $this->safe_value_2($params, 'clientOrderId', 'newClientOrderId');
            if ($newClientOrderId !== null) {
                $request['newClientOrderId'] = $newClientOrderId;
            }
            $request['type'] = $orderType;
            if ($type === 'limit') {
                $request['price'] = $this->price_to_precision($symbol, $price);
                $request['quantity'] = $this->amount_to_precision($symbol, $amount);
            } else if ($type === 'market') {
                // for $market buy it requires the $amount of quote currency to spend
                if ($side === 'buy') {
                    $createMarketBuyOrderRequiresPrice = $this->safe_value($this->options, 'createMarketBuyOrderRequiresPrice', true);
                    if ($createMarketBuyOrderRequiresPrice) {
                        if ($price !== null) {
                            $amount = $amount * $price;
                        } else {
                            throw new InvalidOrder($this->id . " createOrder() requires the $price argument with $market buy orders to calculate total order cost ($amount to spend), where cost = $amount * $price-> Supply a $price argument to createOrder() call if you want the cost to be calculated for you from $price and $amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'amount' argument (the exchange-specific behaviour)");
                        }
                    }
                    $precision = $market['precision']['price'];
                    $request['quantity'] = $this->decimal_to_precision($amount, TRUNCATE, $precision, $this->precisionMode);
                } else {
                    $request['quantity'] = $this->amount_to_precision($symbol, $amount);
                }
            }
        }
        $query = $this->omit($query, array( 'clientOrderId', 'newClientOrderId' ));
        $response = $this->$method (array_merge($request, $query));
        //
        // spot
        //
        //     {
        //         "$symbol":"TBTCBUSDT",
        //         "orderId":"616376654496877056",
        //         "$clientOrderId":"158821382304516955",
        //         "transactTime":"1588213823080",
        //         "$price":"0",
        //         "origQty":"1000",
        //         "executedQty":"0",
        //         "status":"NEW",
        //         "timeInForce":"GTC",
        //         "$type":"MARKET",
        //         "$side":"BUY"
        //     }
        //
        // contract
        //
        //     {
        //         'time' => '1570759718825',
        //         'updateTime' => '0',
        //         'orderId' => '469961015902208000',
        //         'clientOrderId' => '6423344174',
        //         'symbol' => 'BTC-PERP-REV',
        //         'price' => '8200',
        //         'leverage' => '12.08',
        //         'origQty' => '5',
        //         'executedQty' => '0',
        //         'avgPrice' => '0',
        //         'marginLocked' => '0.00005047',
        //         'orderType' => 'LIMIT',
        //         'side' => 'BUY_OPEN',
        //         'fees' => array(),
        //         'timeInForce' => 'GTC',
        //         'status' => 'NEW',
        //         'priceType' => 'INPUT'
        //     }
        //
        return $this->parse_order($response, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $clientOrderId = $this->safe_value_2($params, 'origClientOrderId', 'clientOrderId');
        $request = array();
        $defaultType = $this->safe_string($this->options, 'type', 'spot');
        $options = $this->safe_value($this->options, 'cancelOrder', array());
        $cancelOrderType = $this->safe_string($options, 'type', $defaultType);
        $type = $this->safe_string($params, 'type', $cancelOrderType);
        $query = $this->omit($params, 'type');
        if ($clientOrderId !== null) {
            $request['origClientOrderId'] = $clientOrderId;
            $query = $this->omit($query, array( 'origClientOrderId', 'clientOrderId' ));
        } else {
            $request['orderId'] = $id;
        }
        $method = 'privateDeleteOrder';
        $orderType = $this->safe_string($query, 'orderType');
        if ($orderType !== null) {
            $type = 'future';
        }
        if ($type === 'future') {
            $method = 'contractDeleteOrderCancel';
            if ($orderType === null) {
                throw new ArgumentsRequired($this->id . " cancelOrder() requires an $orderType parameter, pass the array( 'orderType' => 'LIMIT' ) or array( 'orderType' => 'STOP' ) in $params argument");
            }
            $request['orderType'] = $orderType;
        } else {
            if ($type === 'option') {
                $method = 'optionDeleteOrderCancel';
            }
        }
        $response = $this->$method (array_merge($request, $query));
        //
        // spot
        //
        //     {
        //         'exchangeId' => '301',
        //         'symbol' => 'BHTUSDT',
        //         'clientOrderId' => '0',
        //         'orderId' => '499890200602846976',
        //         'status' => 'CANCELED'
        //     }
        //
        // futures
        //
        //     {
        //         "time":"1588353669383",
        //         "updateTime":"0",
        //         "orderId":"617549770304599296",
        //         "$clientOrderId":"test-001",
        //         "$symbol":"BTC-PERP-REV",
        //         "price":"10000",
        //         "leverage":"1",
        //         "origQty":"100",
        //         "executedQty":"0",
        //         "avgPrice":"0",
        //         "marginLocked":"0",
        //         "$orderType":"LIMIT",
        //         "side":"SELL_OPEN",
        //         "fees":array(),
        //         "timeInForce":"GTC",
        //         "status":"CANCELED",
        //         "priceType":"INPUT",
        //     }
        //
        return $this->parse_order($response);
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = null;
        $request = array(
            // if orderId is set, it will get orders < that orderId otherwise most recent orders are returned
            // 'orderId' => '43287482374',
        );
        $defaultType = $this->safe_string($this->options, 'type', 'spot');
        $options = $this->safe_value($this->options, 'fetchOpenOrders', array());
        $fetchOpenOrdersType = $this->safe_string($options, 'type', $defaultType);
        $type = $this->safe_string($params, 'type', $fetchOpenOrdersType);
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            $type = $market['type'];
        }
        $query = $this->omit($params, 'type');
        if ($limit !== null) {
            $request['limit'] = $limit; // default 500, max 1000
        }
        $method = 'privateGetOpenOrders';
        if ($type === 'future') {
            $method = 'contractGetOpenOrders';
        } else if ($type === 'option') {
            $method = 'optionGetOpenOrders';
        }
        $response = $this->$method (array_merge($request, $query));
        //
        // spot
        //
        //     array(
        //         {
        //             'orderId' => '499902955766523648',
        //             'clientOrderId' => '157432907618453',
        //             'exchangeId' => '301',
        //             'symbol' => 'BHTUSDT',
        //             'price' => '0.01',
        //             'origQty' => '50',
        //             'executedQty' => '0',
        //             'cummulativeQuoteQty' => '0',
        //             'avgPrice' => '0',
        //             'status' => 'NEW',
        //             'timeInForce' => 'GTC',
        //             'type' => 'LIMIT',
        //             'side' => 'BUY',
        //             'stopPrice' => '0.0',
        //             'icebergQty' => '0.0',
        //             'time' => '1574329076202',
        //             'updateTime' => '0',
        //             'isWorking' => true
        //         }
        //     )
        //
        // futures
        //
        //     array(
        //         {
        //             "time":"1588353669383",
        //             "updateTime":"0",
        //             "orderId":"617549770304599296",
        //             "clientOrderId":"test-001",
        //             "$symbol":"BTC-PERP-REV",
        //             "price":"10000",
        //             "leverage":"1",
        //             "origQty":"100",
        //             "executedQty":"0",
        //             "avgPrice":"0",
        //             "marginLocked":"0.01",
        //             "orderType":"LIMIT",
        //             "side":"SELL_OPEN",
        //             "fees":array(),
        //             "timeInForce":"GTC",
        //             "status":"NEW",
        //             "priceType":"INPUT"
        //         }
        //     )
        //
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = null;
        $request = array(
            // if orderId is set, it will get orders < that orderId otherwise most recent orders are returned
            // 'orderId' => '43287482374',
            // 'endTime' => $this->milliseconds(), // optional
        );
        $defaultType = $this->safe_string($this->options, 'type', 'spot');
        $options = $this->safe_value($this->options, 'fetchClosedOrders', array());
        $fetchClosedOrdersType = $this->safe_string($options, 'type', $defaultType);
        $type = $this->safe_string($params, 'type', $fetchClosedOrdersType);
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            $type = $market['type'];
        }
        $query = $this->omit($params, 'type');
        if ($limit !== null) {
            $request['limit'] = $limit; // default 500, max 1000
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $method = 'privateGetHistoryOrders';
        if ($type === 'future') {
            $method = 'contractGetHistoryOrders';
        } else if ($type === 'option') {
            $method = 'optionGetHistoryOrders';
        }
        $response = $this->$method (array_merge($request, $query));
        //
        // spot
        //
        //     array(
        //         {
        //             "orderId":"616384027202542080",
        //             "clientOrderId":"158821470194414688",
        //             "exchangeId":"301",
        //             "$symbol":"TBTCBUSDT",
        //             "price":"0",
        //             "origQty":"0.1",
        //             "executedQty":"0.1",
        //             "cummulativeQuoteQty":"682.606",
        //             "avgPrice":"6826.06",
        //             "status":"FILLED",
        //             "timeInForce":"GTC",
        //             "$type":"MARKET",
        //             "side":"SELL",
        //             "stopPrice":"0.0",
        //             "icebergQty":"0.0",
        //             "time":"1588214701974",
        //             "updateTime":"0",
        //             "isWorking":true
        //         }
        //     )
        //
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $clientOrderId = $this->safe_value_2($params, 'origClientOrderId', 'clientOrderId');
        $request = array();
        $defaultType = $this->safe_string($this->options, 'type', 'spot');
        $options = $this->safe_value($this->options, 'fetchOrder', array());
        $fetchOrderType = $this->safe_string($options, 'type', $defaultType);
        $type = $this->safe_string($params, 'type', $fetchOrderType);
        $query = $this->omit($params, 'type');
        if ($clientOrderId !== null) {
            $request['origClientOrderId'] = $clientOrderId;
            $query = $this->omit($query, array( 'origClientOrderId', 'clientOrderId' ));
        } else {
            $request['orderId'] = $id;
        }
        $method = 'privateGetOrder';
        if ($type === 'future') {
            $method = 'contractGetGetOrder';
        } else if ($type === 'option') {
            $method = 'optionGetGetOrder';
        }
        $response = $this->$method (array_merge($request, $query));
        return $this->parse_order($response);
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $currency = null;
        $request = array(
            // 'fromId' => 'string', // if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned
        );
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetDepositOrders (array_merge($request, $params));
        //
        //     array(
        //         array(
        //             'time' => '1565769575929',
        //             'orderId' => '428100569859739648',
        //             'token' => 'USDT',
        //             'address' => '',
        //             'addressTag' => '',
        //             'fromAddress' => '',
        //             'fromAddressTag' => '',
        //             'quantity' => '1100',
        //         ),
        //     )
        //
        return $this->parse_transactions($response, $currency, $since, $limit);
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $currency = null;
        $request = array(
            // 'fromId' => 'string', // if fromId is set, it will get deposits > that fromId, otherwise most recent deposits are returned
        );
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['token'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 500, max 1000
        }
        $response = $this->privateGetWithdrawalOrders (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "time":"1536232111669",
        //             "orderId":"90161227158286336",
        //             "accountId":"517256161325920",
        //             "tokenId":"BHC",
        //             "tokenName":"BHC",
        //             "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
        //             "addressExt":"address tag",
        //             "quantity":"14", // Withdrawal qty
        //             "arriveQuantity":"14", // Arrived qty
        //             "statusCode":"PROCESSING_STATUS",
        //             "status":3,
        //             "txid":"",
        //             "txidUrl":"",
        //             "walletHandleTime":"1536232111669",
        //             "feeTokenId":"BHC",
        //             "feeTokenName":"BHC",
        //             "fee":"0.1",
        //             "requiredConfirmNum":0, // Required confirmations
        //             "confirmNum":0, // Confirmations
        //             "kernelId":"", // BEAM and GRIN only
        //             "isInternalTransfer" => false // True if this transfer is internal
        //         }
        //     )
        //
        return $this->parse_transactions($response, $currency, $since, $limit);
    }

    public function withdraw($code, $amount, $address, $tag = null, $params = array ()) {
        $this->check_address($address);
        $this->load_markets();
        $currency = $this->currency($code);
        $clientOrderId = $this->safe_string($params, 'clientOrderId', $this->uuid());
        $request = array(
            'clientOrderId' => $clientOrderId,
            'tokenId' => $currency['id'],
            'address' => $address, // the withdrawal $address must be in current $tag list in your PC/APP client
            'withdrawQuantity' => $amount,
            // 'chainType' => 'OMNI', // OMNI, ERC20, TRC20
        );
        if ($tag !== null) {
            $request['addressExt'] = $tag;
        }
        $response = $this->privatePostWithdraw (array_merge($request, $params));
        //
        //     {
        //         "status" => 0,
        //         "success" => true,
        //         "needBrokerAudit" => false, // Whether this $request needs broker auit
        //         "orderId" => "423885103582776064" // Id for successful withdrawal
        //     }
        //
        return array(
            'info' => $response,
            'id' => $this->safe_string($response, 'orderId'),
        );
    }

    public function fetch_accounts($params = array ()) {
        $response = $this->privatePostSubAccountQuery ($params);
        //
        //     array(
        //         array(
        //             "$accountId" => "122216245228131",
        //             "accountName" => "createSubAccountByCurl", // sub-$account name
        //             "$accountType" => 1, // 1 token trading, 2 options, 3 futures
        //             "accountIndex" => 1, // 0 main $account, 1 sub-$account
        //         ),
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $account = $response[$i];
            $accountId = $this->safe_string($account, 'accountId');
            $accountType = $this->safe_string($account, 'accountType');
            $type = $accountType;
            if ($accountType === '1') {
                $type = 'spot';
            } else if ($accountType === '2') {
                $type = 'option';
            } else if ($accountType === '3') {
                $type = 'future';
            }
            $result[] = array(
                'id' => $accountId,
                'type' => $type,
                'currency' => null,
                'info' => $account,
            );
        }
        return $result;
    }

    public function fetch_ledger($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'accountType' => 1, // spot 1, options 2, futures 3
            'accountIndex' => 0, // main 0, sub-account 1
            'fromFlowId' => '', // flowId to start from
            'endFlowId' => '', // flowId to end with
            'endTime' => 1588450533040,
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['tokenId'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 500, max 500
        }
        $response = $this->privateGetBalanceFlow (array_merge($request, $params));
        //
        //     array(
        //         array(
        //             "id" => "539870570957903104",
        //             "accountId" => "122216245228131",
        //             "tokenId" => "BTC",
        //             "tokenName" => "BTC",
        //             "flowTypeValue" => 51,
        //             "flowType" => "USER_ACCOUNT_TRANSFER",
        //             "flowName" => "Transfer",
        //             "change" => "-12.5",
        //             "total" => "379.624059937852365", // after change
        //             "created" => "1579093587214"
        //         ),
        //         {
        //             "id" => "536072393645448960",
        //             "accountId" => "122216245228131",
        //             "tokenId" => "USDT",
        //             "tokenName" => "USDT",
        //             "flowTypeValue" => 7,
        //             "flowType" => "AIRDROP",
        //             "flowName" => "Airdrop",
        //             "change" => "-2000",
        //             "total" => "918662.0917630848",
        //             "created" => "1578640809195"
        //         }
        //     )
        //
        return $this->parse_ledger($response, $currency, $since, $limit);
    }

    public function parse_ledger_entry($item, $currency = null) {
        //
        //     {
        //         "$id" => "539870570957903104",
        //         "accountId" => "122216245228131",
        //         "tokenId" => "BTC",
        //         "tokenName" => "BTC",
        //         "flowTypeValue" => 51,
        //         "flowType" => "USER_ACCOUNT_TRANSFER",
        //         "flowName" => "Transfer",
        //         "change" => "-12.5",
        //         "total" => "379.624059937852365", // $after change
        //         "created" => "1579093587214"
        //     }
        //
        //     {
        //         "$id" => "536072393645448960",
        //         "accountId" => "122216245228131",
        //         "tokenId" => "USDT",
        //         "tokenName" => "USDT",
        //         "flowTypeValue" => 7,
        //         "flowType" => "AIRDROP",
        //         "flowName" => "Airdrop",
        //         "change" => "-2000",
        //         "total" => "918662.0917630848",
        //         "created" => "1578640809195"
        //     }
        //
        $currencyId = $this->safe_string($item, 'tokenId');
        $code = $this->safe_currency_code($currencyId, $currency);
        $amount = $this->safe_float($item, 'change');
        $after = $this->safe_float($item, 'total');
        $direction = ($amount < 0) ? 'out' : 'in';
        $before = null;
        if ($after !== null && $amount !== null) {
            $difference = ($direction === 'out') ? $amount : -$amount;
            $before = $this->sum($after, $difference);
        }
        $timestamp = $this->safe_integer($item, 'created');
        $type = $this->parse_ledger_entry_type($this->safe_string($item, 'flowType'));
        $id = $this->safe_string($item, 'id');
        $account = $this->safe_string($item, 'accountId');
        return array(
            'id' => $id,
            'currency' => $code,
            'account' => $account,
            'referenceAccount' => null,
            'referenceId' => null,
            '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(
            'TRADE' => 'trade',
            'FEE' => 'fee',
            'TRANSFER' => 'transfer',
            'DEPOSIT' => 'transaction',
            'MAKER_REWARD' => 'rebate',
            'PNL' => 'pnl',
            'SETTLEMENT' => 'settlement',
            'LIQUIDATION' => 'liquidation',
            'FUNDING_SETTLEMENT' => 'settlement',
            'USER_ACCOUNT_TRANSFER' => 'transfer',
            'OTC_BUY_COIN' => 'trade',
            'OTC_SELL_COIN' => 'trade',
            'OTC_FEE' => 'fee',
            'OTC_TRADE' => 'trade',
            'ACTIVITY_AWARD' => 'referral',
            'INVITATION_REFERRAL_BONUS' => 'referral',
            'REGISTER_BONUS' => 'referral',
            'AIRDROP' => 'airdrop',
            'MINE_REWARD' => 'reward',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            'BROKER_AUDITING_STATUS' => 'pending',
            'BROKER_REJECT_STATUS' => 'failed',
            'AUDITING_STATUS' => 'pending',
            'AUDIT_REJECT_STATUS' => 'failed',
            'PROCESSING_STATUS' => 'pending',
            'WITHDRAWAL_SUCCESS_STATUS' => 'ok',
            'WITHDRAWAL_FAILURE_STATUS' => 'failed',
            'BLOCK_MINING_STATUS' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // fetchDeposits
        //
        //     {
        //         'time' => '1565769575929',
        //         'orderId' => '428100569859739648',
        //         'token' => 'USDT',
        //         'address' => '',
        //         'addressTag' => '',
        //         'fromAddress' => '',
        //         'fromAddressTag' => '',
        //         'quantity' => '1100',
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "time":"1536232111669",
        //         "orderId":"90161227158286336",
        //         "accountId":"517256161325920",
        //         "tokenId":"BHC",
        //         "tokenName":"BHC",
        //         "$address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
        //         "addressExt":"$address $tag",
        //         "quantity":"14", // Withdrawal qty
        //         "arriveQuantity":"14", // Arrived qty
        //         "statusCode":"PROCESSING_STATUS",
        //         "$status":3,
        //         "$txid":"",
        //         "txidUrl":"",
        //         "walletHandleTime":"1536232111669",
        //         "feeTokenId":"BHC",
        //         "feeTokenName":"BHC",
        //         "$fee":"0.1",
        //         "requiredConfirmNum":0, // Required confirmations
        //         "confirmNum":0, // Confirmations
        //         "kernelId":"", // BEAM and GRIN only
        //         "isInternalTransfer" => false // True if this transfer is internal
        //     }
        //
        $id = $this->safe_string($transaction, 'orderId');
        $address = $this->safe_string($transaction, 'address');
        $tag = $this->safe_string_2($transaction, 'addressExt', 'addressTag');
        if ($tag !== null) {
            if (strlen($tag) < 1) {
                $tag = null;
            }
        }
        $addressFrom = $this->safe_string($transaction, 'fromAddress');
        $tagFrom = $this->safe_string($transaction, 'fromAddressTag');
        if ($tagFrom !== null) {
            if (strlen($tagFrom) < 1) {
                $tagFrom = null;
            }
        }
        $currencyId = $this->safe_string($transaction, 'tokenId');
        $code = $this->safe_currency_code($currencyId, $currency);
        $timestamp = $this->safe_integer($transaction, 'time');
        $txid = $this->safe_string($transaction, 'txid');
        if ($txid === '') {
            $txid = null;
        }
        $type = null;
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'statusCode'));
        if ($status === null) {
            $type = 'deposit';
            $status = 'ok';
        } else {
            $type = 'withdrawal';
        }
        $amount = $this->safe_float($transaction, 'quantity');
        $feeCost = $this->safe_float($transaction, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $feeCurrencyId = $this->safe_string($transaction, 'feeTokenId');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'currency' => $feeCurrencyCode,
                'cost' => $feeCost,
            );
        }
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'addressFrom' => $addressFrom,
            'address' => $address,
            'addressTo' => $address,
            'tagFrom' => $tagFrom,
            'tag' => $tag,
            'tagTo' => $tag,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'fee' => $fee,
        );
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker, fetchTickers
        //
        //     {
        //         "time":1588069860794,
        //         "$symbol":"BNB0501PS16",
        //         "bestBidPrice":"0.2129",
        //         "bestAskPrice":"0.3163",
        //         "volume":"33547",
        //         "$quoteVolume":"10801.987",
        //         "lastPrice":"0.2625",
        //         "highPrice":"0.3918",
        //         "lowPrice":"0.2625",
        //         "openPrice":"0.362",
        //     }
        //
        // fetchBidAsk, fetchBidAsks
        //
        //     {
        //         "$symbol" => "LTCBTC",
        //         "bidPrice" => "4.00000000",
        //         "bidQty" => "431.00000000",
        //         "askPrice" => "4.00000200",
        //         "askQty" => "9.00000000"
        //     }
        //
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->safe_integer($ticker, 'time');
        $open = $this->safe_float($ticker, 'openPrice');
        $close = $this->safe_float($ticker, 'lastPrice');
        $change = null;
        $percentage = null;
        $average = null;
        if (($open !== null) && ($close !== null)) {
            $change = $close - $open;
            $average = $this->sum($open, $close) / 2;
            if (($close !== null) && ($close > 0)) {
                $percentage = ($change / $open) * 100;
            }
        }
        $quoteVolume = $this->safe_float($ticker, 'quoteVolume');
        $baseVolume = $this->safe_float($ticker, 'volume');
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'highPrice'),
            'low' => $this->safe_float($ticker, 'lowPrice'),
            'bid' => $this->safe_float_2($ticker, 'bestBidPrice', 'bidPrice'),
            'bidVolume' => $this->safe_float($ticker, 'bidQty'),
            'ask' => $this->safe_float_2($ticker, 'bestAskPrice', 'askPrice'),
            'askVolume' => $this->safe_float($ticker, 'askQty'),
            'vwap' => $vwap,
            'open' => $open,
            'close' => $close,
            'last' => $close,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
    }

    public function parse_trade($trade, $market) {
        //
        // fetchTrades (public)
        //
        //     {
        //         "$price":"0.025344",
        //         "time":1588084082060,
        //         "qty":"1",
        //         "isBuyerMaker":false
        //     }
        //
        // fetchMyTrades (private)
        //
        // spot
        //
        //     {
        //         "$id":"616384027512920576",
        //         "$symbol":"TBTCBUSDT",
        //         "$orderId":"616384027202542080",
        //         "matchOrderId":"605124954767266560",
        //         "$price":"6826.06",
        //         "qty":"0.1",
        //         "commission":"0.682606",
        //         "commissionAsset":"BUSDT",
        //         "time":"1588214701982",
        //         "$isBuyer":false,
        //         "$isMaker":false,
        //         "$fee":{
        //             "feeTokenId":"BUSDT",
        //             "feeTokenName":"BUSDT",
        //             "$fee":"0.682606"
        //         }
        //     }
        //
        $id = $this->safe_string($trade, 'id');
        $timestamp = $this->safe_float($trade, 'time');
        $type = null;
        $orderId = $this->safe_string($trade, 'orderId');
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float($trade, 'qty');
        $cost = null;
        if ($price !== null) {
            if ($amount !== null) {
                $cost = $price * $amount;
            }
        }
        $side = null;
        $takerOrMaker = null;
        if (is_array($trade) && array_key_exists('isBuyerMaker', $trade)) {
            $side = $trade['isBuyerMaker'] ? 'sell' : 'buy';
        } else {
            $isMaker = $this->safe_value($trade, 'isMaker');
            if ($isMaker !== null) {
                $takerOrMaker = $isMaker ? 'maker' : 'taker';
            }
            $isBuyer = $this->safe_value($trade, 'isBuyer');
            $side = $isBuyer ? 'buy' : 'sell';
        }
        $fee = null;
        $feeCost = $this->safe_float($trade, 'commission');
        if ($feeCost !== null) {
            $feeCurrencyId = $this->safe_string($trade, 'commissionAsset');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
            );
        }
        $symbol = null;
        if (($symbol === null) && ($market !== null)) {
            $symbol = $market['symbol'];
        }
        return array(
            'id' => $id,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => $type,
            'order' => $orderId,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function parse_order($order, $market = null) {
        //
        // createOrder
        //
        //     {
        //         "$symbol":"TBTCBUSDT",
        //         "orderId":"616376654496877056",
        //         "$clientOrderId":"158821382304516955",
        //         "transactTime":"1588213823080",
        //         "$price":"0",
        //         "origQty":"1000",
        //         "executedQty":"0",
        //         "$status":"NEW",
        //         "$timeInForce":"GTC",
        //         "$type":"MARKET",
        //         "$side":"BUY"
        //     }
        //
        // fetchOrder, fetchOpenOrders, fetchClosedOrders
        //
        // spot
        //
        //     {
        //         "orderId":"616384027202542080",
        //         "$clientOrderId":"158821470194414688",
        //         "exchangeId":"301",
        //         "$symbol":"TBTCBUSDT",
        //         "$price":"0",
        //         "origQty":"0.1",
        //         "executedQty":"0.1",
        //         "cummulativeQuoteQty":"682.606",
        //         "avgPrice":"6826.06",
        //         "$status":"FILLED",
        //         "$timeInForce":"GTC",
        //         "$type":"MARKET",
        //         "$side":"SELL",
        //         "$stopPrice":"0.0",
        //         "icebergQty":"0.0",
        //         "time":"1588214701974",
        //         "updateTime":"0",
        //         "isWorking":true
        //     }
        //
        // future
        //
        //     {
        //         time => "1588353669383",
        //         updateTime => "0",
        //         orderId => "617549770304599296",
        //         $clientOrderId => "test-001",
        //         $symbol => "BTC-PERP-REV",
        //         $price => "10000",
        //         leverage => "1",
        //         origQty => "100",
        //         executedQty => "0",
        //         avgPrice => "0",
        //         marginLocked => "0",
        //         orderType => "LIMIT",
        //         $side => "SELL_OPEN",
        //         $fees => array(),
        //         $timeInForce => "GTC",
        //         $status => "CANCELED",
        //         priceType => "INPUT"
        //     }
        //
        //
        $id = $this->safe_string($order, 'orderId');
        $clientOrderId = $this->safe_string($order, 'clientOrderId');
        $timestamp = $this->safe_integer($order, 'time');
        if ($timestamp === null) {
            $timestamp = $this->safe_integer($order, 'transactTime');
        }
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $type = $this->safe_string_lower($order, 'type');
        $side = $this->safe_string_lower($order, 'side');
        $price = $this->safe_float($order, 'price');
        $average = $this->safe_float($order, 'avgPrice');
        $amount = null;
        $cost = $this->safe_float($order, 'cummulativeQuoteQty');
        $filled = null;
        $remaining = null;
        if ($type === null) {
            $type = $this->safe_string_lower($order, 'orderType');
            if (($market !== null) && $market['inverse']) {
                $cost = $this->safe_float($order, 'executedQty');
                $amount = null;
            }
            if ($cost === 0.0) {
                $filled = 0;
            }
        } else {
            $amount = $this->safe_float($order, 'origQty');
            if ($type === 'market') {
                $price = null;
                if ($side === 'buy') {
                    $amount = null;
                }
            }
            $filled = $this->safe_float($order, 'executedQty');
            if ($filled !== null) {
                if ($amount !== null) {
                    $remaining = $amount - $filled;
                }
            }
        }
        if ($average === 0.0) {
            $average = null;
        }
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $timeInForce = $this->safe_string($order, 'timeInForce');
        $stopPrice = $this->safe_float($order, 'stopPrice');
        $result = array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'average' => $average,
            'cost' => $cost,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'trades' => null,
            'fee' => null,
            'fees' => null,
        );
        $fees = $this->safe_value($order, 'fees', array());
        $numFees = is_array($fees) ? count($fees) : 0;
        if ($numFees > 0) {
            $result['fees'] = array();
            for ($i = 0; $i < count($fees); $i++) {
                $feeCost = $this->safe_float($fees[$i], 'fee');
                if ($feeCost !== null) {
                    $feeCurrencyId = $this->safe_string($fees[$i], 'feeToken');
                    $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
                    $result['fees'][] = array(
                        'cost' => $feeCost,
                        'currency' => $feeCurrencyCode,
                    );
                }
            }
        }
        return $result;
    }

    public function parse_order_status($status) {
        $statuses = array(
            'NEW' => 'open',
            'CANCELED' => 'canceled',
            'FILLED' => 'closed',
            'PARTIALLY_FILLED' => 'open',
            'PENDING_CANCEL' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->urls['api'][$api] . '/' . $this->version . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        $isPublicContract = ($api === 'contract') && (($path === 'insurance') || ($path === 'fundingRate'));
        if (($api === 'public') || ($api === 'quote') || $isPublicContract) {
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        } else {
            $timestamp = $this->milliseconds();
            $this->check_required_credentials();
            $request = array_merge(array(
                'timestamp' => $timestamp,
            ), $query);
            // 准备待签名数据
            $auth = $this->urlencode($request);
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
            $request['signature'] = $signature;
            $headers = array(
                'X-BH-APIKEY' => $this->apiKey,
            );
            if ($method === 'POST') {
                $body = $this->urlencode($request);
                $headers = array_merge(array(
                    'Content-Type' => 'application/x-www-form-urlencoded',
                ), $headers);
            } else {
                $url .= '?' . $this->urlencode($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 === null) {
            return; // fallback to default error handler
        }
        if (is_array($response) && array_key_exists('code', $response)) {
            $code = $this->safe_string($response, 'code');
            if ($code !== '0') {
                $feedback = $this->id . ' ' . $body;
                $this->throw_exactly_matched_exception($this->exceptions['exact'], $code, $feedback);
                throw new ExchangeError($feedback);
            }
        }
    }
}
