<?php

namespace ccxt\async;

// 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\ArgumentsRequired;
use \ccxt\BadRequest;
use \ccxt\BadSymbol;
use \ccxt\OrderNotFound;

class aax extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'aax',
            'name' => 'AAX',
            'countries' => array( 'MT' ), // Malta
            'enableRateLimit' => true,
            'rateLimit' => 500,
            'version' => 'v2',
            'hostname' => 'aaxpro.com', // aax.com
            'certified' => true,
            'has' => array(
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'createOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchCanceledOrders' => true,
                'fetchClosedOrders' => true,
                'fetchDepositAddress' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchTicker' => 'emulated',
                'fetchTickers' => true,
                'fetchTrades' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '4h' => '4h',
                '12h' => '12h',
                '1d' => '1d',
                '3d' => '3d',
                '1w' => '1w',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/104140087-a27f2580-53c0-11eb-87c1-5d9e81208fe9.jpg',
                'test' => array(
                    'v1' => 'https://api.testnet.{hostname}/marketdata/v1',
                    'public' => 'https://api.testnet.{hostname}',
                    'private' => 'https://api.testnet.{hostname}',
                ),
                'api' => array(
                    'v1' => 'https://api.{hostname}/marketdata/v1',
                    'public' => 'https://api.{hostname}',
                    'private' => 'https://api.{hostname}',
                ),
                'www' => 'https://www.aaxpro.com', // string website URL
                'doc' => 'https://www.aaxpro.com/apidoc/index.html',
                'fees' => 'https://www.aaxpro.com/en-US/fees/',
                'referral' => 'https://www.aaxpro.com/invite/sign-up?inviteCode=JXGm5Fy7R2MB',
            ),
            'api' => array(
                'v1' => array(
                    'get' => array(
                        'getHistMarketData', // Get OHLC k line of specific market
                    ),
                ),
                'public' => array(
                    // these endpoints are not documented
                    // 'get' => array(
                    //     'order_book', // Get the order book of specified market
                    //     'order_book/{market}',
                    //     'trades', // Get recent trades on market, each trade is included only once Trades are sorted in reverse creation order.
                    //     'trades/{market}',
                    //     'tickers', // Get ticker of all markets
                    //     'tickers/{market}', // Get ticker of specific market
                    // ),
                    'get' => array(
                        'announcement/maintenance', // System Maintenance Notice
                        'instruments', // Retrieve all trading pairs information
                        'market/orderbook', // Order Book
                        'futures/position/openInterest', // Open Interest
                        'market/tickers', // Get the Last 24h Market Summary
                        'market/candles', // Get Current Candlestick
                        'market/history/candles', // Get Current Candlestick
                        'market/trades', // Get the Most Recent Trades
                        'market/markPrice', // Get Current Mark Price
                        'futures/funding/predictedFunding/{symbol}', // Get Predicted Funding Rate
                        'futures/funding/prevFundingRate/{symbol}', // Get Last Funding Rate
                        'market/candles/index', // Get Current Index Candlestick
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'user/info', // Retrieve user information
                        'account/balances', // Get Account Balances
                        'account/deposit/address', // undocumented
                        'spot/trades', // Retrieve trades details for a spot order
                        'spot/openOrders', // Retrieve spot open orders
                        'spot/orders', // Retrieve historical spot orders
                        'futures/position', // Get positions for all contracts
                        'futures/position/closed', // Get closed positions
                        'futures/trades', // Retrieve trade details for a futures order
                        'futures/openOrders', // Retrieve futures open orders
                        'futures/orders', // Retrieve historical futures orders
                        'futures/funding/predictedFundingFee/{symbol}', // Get predicted funding fee
                    ),
                    'post' => array(
                        'account/transfer', // Asset Transfer
                        'spot/orders', // Create a new spot order
                        'spot/orders/cancelAllOnTimeout', // Automatically cancel all your spot orders after a specified timeout.
                        'futures/orders', // Create a new futures order
                        'futures/orders/cancelAllOnTimeout', // Automatically cancel all your futures orders after a specified timeout.
                        'futures/position/sltp', // Set take profit and stop loss orders for an opening position
                        'futures/position/close', // Close position
                        'futures/position/leverage', // Update leverage for position
                        'futures/position/margin', // Modify Isolated Position Margin
                    ),
                    'put' => array(
                        'spot/orders', // Amend spot order
                        'futures/orders', // Amend the quantity of an open futures order
                    ),
                    'delete' => array(
                        'spot/orders/cancel/{orderID}', // Cancel a spot order
                        'spot/orders/cancel/all', // Batch cancel spot orders
                        'futures/orders/cancel/{orderID}', // Cancel a futures order
                        'futures/orders/cancel/all', // Batch cancel futures orders
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'maker' => 0.06 / 100,
                    'taker' => 0.10 / 100,
                ),
                'funding' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'withdraw' => array(), // There is only 1% fee on withdrawals to your bank account.
                ),
            ),
            'commonCurrencies' => array(
                'XBT' => 'XBT',
            ),
            'exceptions' => array(
                'exact' => array(
                    '2002' => '\\ccxt\\InsufficientFunds',
                    '2003' => '\\ccxt\\OrderNotFound',
                    '10003' => '\\ccxt\\BadRequest', // Parameter validation error
                    '10006' => '\\ccxt\\AuthenticationError', // Session expired, please relogin
                    '10007' => '\\ccxt\\AuthenticationError', // Invalid authentication key or token
                    '11007' => '\\ccxt\\AuthenticationError', // Invalid key format
                    '20001' => '\\ccxt\\InsufficientFunds', // Insufficient balance. Please deposit to trade.
                    '20009' => '\\ccxt\\InvalidOrder', // Order amount must be positive
                    '30000' => '\\ccxt\\OrderNotFound', // array("code":30000,"data":null,"message":"The order does not exist","ts":1610259732263)
                    '30001' => '\\ccxt\\InvalidOrder', // The order is being submitted, please try again later
                    '30004' => '\\ccxt\\InvalidOrder', // Minimum quantity is {0}
                    '30005' => '\\ccxt\\InvalidOrder', // Quantity maximum precision is {0} decimal places
                    '30006' => '\\ccxt\\InvalidOrder', // Price maximum precision is {0} decimal places
                    '30007' => '\\ccxt\\InvalidOrder', // Minimum price is {0}
                    '30008' => '\\ccxt\\InvalidOrder', // Stop price maximum precision is {0} decimal places
                    '30009' => '\\ccxt\\InvalidOrder', // Stop Price cannot be less than {0}
                    '30010' => '\\ccxt\\InvalidOrder', // Market price cannot be empty
                    '30011' => '\\ccxt\\CancelPending', // The order is being cancelled, please wait.
                    '30012' => '\\ccxt\\BadRequest', // Unknown currency
                    '30013' => '\\ccxt\\BadSymbol', // Unknown symbol
                    '30014' => '\\ccxt\\OrderNotFound', // Futures order cannot be found
                    '30015' => '\\ccxt\\InvalidOrder', // This is not an open order and cannot modified
                    '30016' => '\\ccxt\\ExchangeError', // No position found
                    '30017' => '\\ccxt\\InvalidOrder', // The current close position is 0. It is recommended that you cancel the current order closing order.
                    '30018' => '\\ccxt\\InvalidOrder', // Order price cannot be greater than {0}
                    '30019' => '\\ccxt\\InvalidOrder', // Order quantity cannot be greater than {0}
                    '30020' => '\\ccxt\\InvalidOrder', // Order price must be a multiple of {0}
                    '30021' => '\\ccxt\\InvalidOrder', // Margin adjustement must be greater than 0
                    '30022' => '\\ccxt\\InvalidOrder', // New quantity must be greater than filled quantity
                    '30023' => '\\ccxt\\InvalidOrder', // Order failed, please try again
                    '30024' => '\\ccxt\\InvalidOrder', // TimeInForce error, only GTC or IOC are allowed
                    '30025' => '\\ccxt\\InvalidOrder', // TimeInForce error, only GTC is allowed
                    '30026' => '\\ccxt\\InvalidOrder', // Quantity is not a multiple of {0}
                    '30027' => '\\ccxt\\InvalidOrder', // Close position failed, it is recommended that you cancel the current order and then close the position.
                    '30028' => '\\ccxt\\BadSymbol', // Symbol cannot be traded at this time
                    '30029' => '\\ccxt\\InvalidOrder', // Modified quantity or price cannot be empty
                    '30030' => '\\ccxt\\InvalidOrder', // Price cannot be specified for market orders
                    '30031' => '\\ccxt\\InvalidOrder', // Liquidation orders cannot be modified
                    '30032' => '\\ccxt\\InvalidOrder', // Leverage cannot be greater than {0}
                    '30033' => '\\ccxt\\InvalidOrder', // Leverage cannot be smaller than {0}
                    '30034' => '\\ccxt\\RateLimitExceeded', // The max number of open orders is {0}. To place a new order, please cancel a previous one
                    '30035' => '\\ccxt\\RateLimitExceeded', // The max number of {0} open orders is {1}. To place a new order, please cancel a previous one
                    '30036' => '\\ccxt\\ExchangeNotAvailable', // Liquidation is in progress, please try again later
                    '30037' => '\\ccxt\\InvalidOrder', // Once stop limit order triggered, stop price cannot be amended
                    '30038' => '\\ccxt\\ExchangeError', // The total value of your orders has exceeded the current risk limit. Please adjust the risk limit
                    '30039' => '\\ccxt\\InsufficientFunds', // Your risk limit has now been changed to {0}, your maximum leverage less than 1, please readjust accordingly
                    '30040' => '\\ccxt\\InvalidOrder', // Order status has changed, please try again later
                    '30041' => '\\ccxt\\InvalidOrder', // Liquidation orders cannot be cancelled
                    '30042' => '\\ccxt\\InvalidOrder', // Order cannot be placed as you will be breaching you max limit value of {1} BTC for {0}
                    '30043' => '\\ccxt\\InvalidOrder', // The risk limit cannot be less than 0
                    '30044' => '\\ccxt\\BadRequest', // Timeout cannot be greater than 60 minutes
                    '30045' => '\\ccxt\\InvalidOrder', // Side is not valid, it should be BUY or SELL
                    '30046' => '\\ccxt\\InvalidOrder', // Order type is not valid, it should be MARKET or LIMIT or STOP-LIMIT or STOP
                    '30047' => '\\ccxt\\InvalidOrder', // The order is closed. Can't cancel
                    '30048' => '\\ccxt\\InvalidOrder', // Market orders cannot be modified
                    '30049' => '\\ccxt\\InvalidOrder', // The order is being modified, please wait
                    '30050' => '\\ccxt\\InvalidOrder', // Maximum 10 orders
                    '40004' => '\\ccxt\\BadRequest', // Requested resource doesn't exist
                    '40009' => '\\ccxt\\RateLimitExceeded', // Too many requests
                    '40102' => '\\ccxt\\AuthenticationError', // array("code":40102,"message":"Unauthorized(invalid key)")
                    '40103' => '\\ccxt\\AuthenticationError', // array("code":40103,"message":"Unauthorized(invalid sign)")
                    '40303' => '\\ccxt\\PermissionDenied', // array("code":40303,"message":"Forbidden(invalid scopes)")
                    '41001' => '\\ccxt\\BadRequest', // Incorrect HTTP request
                    '41002' => '\\ccxt\\BadRequest', // Unsupported HTTP request method
                    '42001' => '\\ccxt\\ExchangeNotAvailable', // Duplicated data entry, please check and try again
                    '50001' => '\\ccxt\\ExchangeError', // Server side exception, please try again later
                    '50002' => '\\ccxt\\ExchangeError', // Server is busy, please try again later
                ),
                'broad' => array(),
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                'defaultType' => 'spot', // 'spot', 'future'
            ),
        ));
    }

    public function fetch_markets($params = array ()) {
        $response = yield $this->publicGetInstruments ($params);
        //
        //     {
        //         "code":1,
        //         "message":"success",
        //         "ts":1610159448962,
        //         "$data":array(
        //             array(
        //                 "tickSize":"0.01",
        //                 "lotSize":"1",
        //                 "$base":"BTC",
        //                 "$quote":"USDT",
        //                 "minQuantity":"1.0000000000",
        //                 "maxQuantity":"30000",
        //                 "minPrice":"0.0100000000",
        //                 "maxPrice":"999999.0000000000",
        //                 "$status":"readOnly",
        //                 "$symbol":"BTCUSDTFP",
        //                 "code":"FP",
        //                 "takerFee":"0.00040",
        //                 "makerFee":"0.00020",
        //                 "multiplier":"0.001000000000",
        //                 "mmRate":"0.00500",
        //                 "imRate":"0.01000",
        //                 "$type":"$futures",
        //                 "$settleType":"Vanilla",
        //                 "settleCurrency":"USDT"
        //             ),
        //             array(
        //                 "tickSize":"0.5",
        //                 "lotSize":"10",
        //                 "$base":"BTC",
        //                 "$quote":"USD",
        //                 "minQuantity":"10.0000000000",
        //                 "maxQuantity":"300000",
        //                 "minPrice":"0.5000000000",
        //                 "maxPrice":"999999.0000000000",
        //                 "$status":"readOnly",
        //                 "$symbol":"BTCUSDFP",
        //                 "code":"FP",
        //                 "takerFee":"0.00040",
        //                 "makerFee":"0.00020",
        //                 "multiplier":"1.000000000000",
        //                 "mmRate":"0.00500",
        //                 "imRate":"0.01000",
        //                 "$type":"$futures",
        //                 "$settleType":"Inverse",
        //                 "settleCurrency":"BTC"
        //             ),
        //             array(
        //                 "tickSize":"0.0001",
        //                 "lotSize":"0.01",
        //                 "$base":"AAB",
        //                 "$quote":"USDT",
        //                 "minQuantity":"5.0000000000",
        //                 "maxQuantity":"50000.0000000000",
        //                 "minPrice":"0.0001000000",
        //                 "maxPrice":"999999.0000000000",
        //                 "$status":"readOnly",
        //                 "$symbol":"AABUSDT",
        //                 "code":null,
        //                 "takerFee":"0.00100",
        //                 "makerFee":"0.00100",
        //                 "multiplier":"1.000000000000",
        //                 "mmRate":"0.02500",
        //                 "imRate":"0.05000",
        //                 "$type":"$spot",
        //                 "$settleType":null,
        //                 "settleCurrency":null
        //             ),
        //         )
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $market = $data[$i];
            $id = $this->safe_string($market, 'symbol');
            $baseId = $this->safe_string($market, 'base');
            $quoteId = $this->safe_string($market, 'quote');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $status = $this->safe_string($market, 'status');
            $active = ($status === 'enable');
            $taker = $this->safe_float($market, 'takerFee');
            $maker = $this->safe_float($market, 'makerFee');
            $type = $this->safe_string($market, 'type');
            $inverse = null;
            $linear = null;
            $quanto = null;
            $spot = ($type === 'spot');
            $futures = ($type === 'futures');
            $settleType = $this->safe_string_lower($market, 'settleType');
            if ($settleType !== null) {
                $inverse = ($settleType === 'inverse');
                $linear = ($settleType === 'vanilla');
                $quanto = ($settleType === 'quanto');
            }
            $symbol = $id;
            if ($type === 'spot') {
                $symbol = $base . '/' . $quote;
            }
            $precision = array(
                'amount' => $this->safe_float($market, 'lotSize'),
                'price' => $this->safe_float($market, 'tickSize'),
            );
            $result[] = array(
                'id' => $id,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'type' => $type,
                'spot' => $spot,
                'futures' => $futures,
                'inverse' => $inverse,
                'linear' => $linear,
                'quanto' => $quanto,
                'precision' => $precision,
                'info' => $market,
                'active' => $active,
                'taker' => $taker,
                'maker' => $maker,
                'percentage' => false,
                'tierBased' => true,
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_string($market, 'minQuantity'),
                        'max' => $this->safe_string($market, 'maxQuantity'),
                    ),
                    'price' => array(
                        'min' => $this->safe_string($market, 'minPrice'),
                        'max' => $this->safe_string($market, 'maxPrice'),
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
            );
        }
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        //     {
        //         "t":1610162685342, // $timestamp
        //         "a":"0.00000000", // trading volume in USD in the $last 24 hours, futures only
        //         "c":"435.20000000", // close
        //         "d":"4.22953489", // $change
        //         "h":"455.04000000", // high
        //         "l":"412.78000000", // low
        //         "o":"417.54000000", // $open
        //         "s":"BCHUSDTFP", // $market id
        //         "v":"2031068.00000000", // trading volume in quote currency of $last 24 hours
        //     }
        //
        $timestamp = $this->safe_integer($ticker, 't');
        $marketId = $this->safe_string($ticker, 's');
        $symbol = $this->safe_symbol($marketId, $market);
        $last = $this->safe_float($ticker, 'c');
        $open = $this->safe_float($ticker, 'o');
        $change = null;
        $percentage = null;
        $average = null;
        if ($last !== null && $open !== null) {
            $change = $last - $open;
            if ($open > 0) {
                $percentage = $change / $open * 100;
            }
            $average = $this->sum($last, $open) / 2;
        }
        $quoteVolume = $this->safe_float($ticker, 'v');
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'h'),
            'low' => $this->safe_float($ticker, 'l'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => null,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $tickers = yield $this->fetch_tickers(null, $params);
        if (is_array($tickers) && array_key_exists($symbol, $tickers)) {
            return $tickers[$symbol];
        }
        throw new BadSymbol($this->id . ' fetchTicker() $symbol ' . $symbol . ' ticker not found');
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        yield $this->load_markets();
        $response = yield $this->publicGetMarketTickers ($params);
        //
        //     {
        //         "e":"$tickers",
        //         "t":1610162685342,
        //         "$tickers":array(
        //             array(
        //                 "a":"0.00000000",
        //                 "c":"435.20000000",
        //                 "d":"4.22953489",
        //                 "h":"455.04000000",
        //                 "l":"412.78000000",
        //                 "o":"417.54000000",
        //                 "s":"BCHUSDTFP",
        //                 "v":"2031068.00000000",
        //             ),
        //         ),
        //     }
        //
        $tickers = $this->safe_value($response, 'tickers', array());
        $result = array();
        $timestamp = $this->safe_integer($response, 't');
        for ($i = 0; $i < count($tickers); $i++) {
            $ticker = $this->parse_ticker(array_merge($tickers[$i], array( 't' => $timestamp )));
            $result[] = $ticker;
        }
        return $this->filter_by_array($result, 'symbol', $symbols);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        if ($limit === null) {
            $limit = 20;
        } else {
            if (($limit !== 20) && ($limit !== 50)) {
                throw new BadRequest($this->id . ' fetchOrderBook() $limit argument must be null, 20 or 50');
            }
        }
        $request = array(
            'symbol' => $market['id'],
            'level' => $limit, // required
        );
        //
        $response = yield $this->publicGetMarketOrderbook (array_merge($request, $params));
        //
        //     {
        //         "asks":[
        //             ["10823.00000000","0.004000"],
        //             ["10823.10000000","0.100000"],
        //             ["10823.20000000","0.010000"]
        //         ],
        //         "bids":[
        //             ["10821.20000000","0.002000"],
        //             ["10821.10000000","0.005000"],
        //             ["10820.40000000","0.013000"]
        //         ],
        //         "e":"BTCUSDT@book_50",
        //         "t":1561543614756
        //     }
        //
        $timestamp = $this->safe_integer($response, 't'); // need unix type
        return $this->parse_order_book($response, $timestamp);
    }

    public function parse_trade($trade, $market = null) {
        //
        // public fetchTrades
        //
        //     {
        //         "p":"9395.50000000",
        //         "q":"50.000000",
        //         "t":1592563996718
        //     }
        //
        // private fetchMyTrades
        //
        //     {
        //         "avgPrice":"1199.8",
        //         "base":"ETH",
        //         "clOrdID":null,
        //         "commission":"0.00002",
        //         "createTime":"2021-01-11T02:47:51.512Z",
        //         "cumQty":"0.02",
        //         "filledOrderID":"1eUD4F5rwK",
        //         "filledPrice":"1199.8",
        //         "filledQty":"0.02",
        //         "leavesQty":"0",
        //         "oCreateTime":"2021-01-11T02:47:51.377Z",
        //         "orderID":"1eUD4EHfdU",
        //         "orderQty":"0.02",
        //         "orderStatus":3,
        //         "$orderType":1,
        //         "$price":"1198.25",
        //         "quote":"USDT",
        //         "rejectCode":null,
        //         "rejectReason":null,
        //         "$side":1,
        //         "stopPrice":"0",
        //         "$symbol":"ETHUSDT",
        //         "taker":true,
        //         "tradeID":"E04WTIgfmULU",
        //         "transactTime":"2021-01-11T02:47:51.389Z",
        //         "updateTime":null,
        //         "userID":"1362494"
        //     }
        //
        $timestamp = $this->safe_integer($trade, 't');
        if ($timestamp === null) {
            $timestamp = $this->parse8601($this->safe_string($trade, 'createTime'));
        }
        $id = $this->safe_string_2($trade, 'tid', 'tradeID');
        $symbol = null;
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        if ($market !== null) {
            $symbol = $market['symbol'];
        }
        $price = $this->safe_float_2($trade, 'p', 'filledPrice');
        $amount = $this->safe_float_2($trade, 'q', 'filledQty');
        $orderId = $this->safe_string($trade, 'orderID');
        $isTaker = $this->safe_value($trade, 'taker');
        $takerOrMaker = null;
        if ($isTaker !== null) {
            $takerOrMaker = $isTaker ? 'taker' : 'maker';
        }
        $side = $this->safe_string($trade, 'side');
        if ($side === '1') {
            $side = 'buy';
        } else if ($side === '2') {
            $side = 'sell';
        }
        if ($side === null) {
            $side = ($price > 0) ? 'buy' : 'sell';
        }
        $side = ($price > 0) ? 'buy' : 'sell';
        $price = abs($price);
        $cost = null;
        if (($price !== null) && ($amount !== null)) {
            $cost = $price * $amount;
        }
        $orderType = $this->parse_order_type($this->safe_string($trade, 'orderType'));
        $fee = null;
        $feeCost = $this->safe_float($trade, 'commission');
        if ($feeCost !== null) {
            $feeCurrency = null;
            if ($market !== null) {
                if ($side === 'buy') {
                    $feeCurrency = $market['base'];
                } else if ($side === 'sell') {
                    $feeCurrency = $market['quote'];
                }
            }
            $fee = array(
                'currency' => $feeCurrency,
                'cost' => $feeCost,
            );
        }
        return array(
            'info' => $trade,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => $orderType,
            'side' => $side,
            'order' => $orderId,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $limit = ($limit === null) ? 2000 : $limit;
        $limit = min ($limit, 2000);
        $request = array(
            'symbol' => $market['id'],
            'limit' => $limit,
        );
        $response = yield $this->publicGetMarketTrades ($request);
        //
        //     {
        //         "e":"BTCUSDFP@$trades",
        //         "$trades" => array(
        //             array("p":"9395.50000000","q":"50.000000","t":1592563996718),
        //             array("p":"9395.50000000","q":"50.000000","t":1592563993577),
        //         ),
        //     }
        //
        $trades = $this->safe_value($response, 'trades', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     array(
        //         0.042398, // 0 open
        //         0.042684, // 1 high
        //         0.042366, // 2 low
        //         0.042386, // 3 close
        //         0.93734243, // 4 volume
        //         1611514800, // 5 timestamp
        //     )
        //
        return array(
            $this->safe_timestamp($ohlcv, 5),
            $this->safe_float($ohlcv, 0),
            $this->safe_float($ohlcv, 1),
            $this->safe_float($ohlcv, 2),
            $this->safe_float($ohlcv, 3),
            $this->safe_float($ohlcv, 4),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1h', $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            // 'limit' => $limit, // if set counts from now into the past
            'symbol' => $market['id'],
            'timeFrame' => $this->timeframes[$timeframe],
        );
        $limit = ($limit === null) ? 500 : $limit;
        $duration = $this->parse_timeframe($timeframe);
        if ($since === null) {
            $end = $this->seconds();
            $request['start'] = $end - $duration * $limit;
            $request['end'] = $end;
        } else {
            $start = intval($since / 1000);
            $request['start'] = $start;
            $request['end'] = $this->sum($start, $duration * $limit);
        }
        $response = yield $this->publicGetMarketHistoryCandles (array_merge($request, $params));
        //
        //     {
        //         "$data":[
        //             [0.042398,0.042684,0.042366,0.042386,0.93734243,1611514800],
        //             [0.042386,0.042602,0.042234,0.042373,1.01925239,1611518400],
        //             [0.042373,0.042558,0.042362,0.042389,0.93801705,1611522000],
        //         ],
        //         "success":true,
        //         "t":1611875157
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
    }

    public function fetch_balance($params = array ()) {
        yield $this->load_markets();
        $defaultType = $this->safe_string_2($this->options, 'fetchBalance', 'defaultType', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $types = array(
            'spot' => 'SPTP',
            'future' => 'FUTP',
            'otc' => 'F2CP',
            'saving' => 'VLTP',
        );
        $purseType = $this->safe_string($types, $type, $type);
        $request = array(
            'purseType' => $purseType,
        );
        $params = $this->omit($params, 'type');
        $response = yield $this->privateGetAccountBalances (array_merge($request, $params));
        //
        //     {
        //         "$code":1,
        //         "$data":array(
        //             array(
        //                 "$purseType":"FUTP",
        //                 "currency":"BTC",
        //                 "available":"0.41000000",
        //                 "unavailable":"0.00000000"
        //             ),
        //             {
        //                 "$purseType":"FUTP",
        //                 "currency":"USDT",
        //                 "available":"0.21000000",
        //                 "unvaliable":"0.00000000"
        //             }
        //         )
        //         "message":"success",
        //         "ts":1573530401020
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($data); $i++) {
            $balance = $data[$i];
            $balanceType = $this->safe_string($balance, 'purseType');
            if ($balanceType === $purseType) {
                $currencyId = $this->safe_string($balance, 'currency');
                $code = $this->safe_currency_code($currencyId);
                $account = $this->account();
                $account['free'] = $this->safe_float($balance, 'available');
                $account['used'] = $this->safe_float($balance, 'unavailable');
                $result[$code] = $account;
            }
        }
        return $this->parse_balance($result);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $orderType = strtoupper($type);
        $orderSide = strtoupper($side);
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            // 'orderType' => $orderType, // MARKET, LIMIT, STOP, STOP-LIMIT
            'symbol' => $market['id'],
            'orderQty' => $this->amount_to_precision($symbol, $amount),
            'side' => $orderSide,
            // 'stopPrice' => $this->price_to_precision($symbol, $stopPrice),
            // 'clOrdID' => $clientOrderId, // up to 20 chars, lowercase and uppercase letters only
            // 'timeInForce' => 'GTC', // GTC, IOC, FOK, default is GTC
            // 'execInst' => 'Post-Only', // the only value supported by the exchange, futures-only
        );
        $timeInForce = $this->safe_string($params, 'timeInForce');
        if ($timeInForce !== null) {
            $request['timeInForce'] = $timeInForce;
            $params = $this->omit($params, 'timeInForce');
        }
        $clientOrderId = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['clOrdID'] = $clientOrderId;
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
        }
        $stopPrice = $this->safe_float($params, 'stopPrice');
        if ($stopPrice === null) {
            if (($orderType === 'STOP-LIMIT') || ($orderType === 'STOP')) {
                throw new ArgumentsRequired($this->id . ' createOrder() requires a $stopPrice parameter for ' . $orderType . ' orders');
            }
        } else {
            if ($orderType === 'LIMIT') {
                $orderType = 'STOP-LIMIT';
            } else if ($orderType === 'MARKET') {
                $orderType = 'STOP';
            }
            $request['stopPrice'] = $this->price_to_precision($symbol, $stopPrice);
            $params = $this->omit($params, 'stopPrice');
        }
        if ($orderType === 'LIMIT' || $orderType === 'STOP-LIMIT') {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $request['orderType'] = $orderType;
        $method = null;
        if ($market['spot']) {
            $method = 'privatePostSpotOrders';
        } else if ($market['futures']) {
            $method = 'privatePostFuturesOrders';
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "$symbol":"ETHUSDT",
        //             "$orderType":2,
        //             "avgPrice":"0",
        //             "execInst":null,
        //             "orderStatus":0,
        //             "userID":"1362494",
        //             "quote":"USDT",
        //             "rejectReason":null,
        //             "rejectCode":null,
        //             "$price":"1500",
        //             "orderQty":"1",
        //             "commission":"0",
        //             "id":"268323430253735936",
        //             "$timeInForce":1,
        //             "isTriggered":false,
        //             "$side":2,
        //             "orderID":"1eO51MDSpQ",
        //             "leavesQty":"0",
        //             "cumQty":"0",
        //             "updateTime":null,
        //             "lastQty":"0",
        //             "clOrdID":null,
        //             "$stopPrice":null,
        //             "createTime":null,
        //             "transactTime":null,
        //             "base":"ETH",
        //             "lastPrice":"0"
        //         ),
        //         "message":"success",
        //         "ts":1610245290980
        //     }
        //
        // futures
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "liqType":0,
        //             "$symbol":"ETHUSDTFP",
        //             "$orderType":2,
        //             "leverage":"1",
        //             "marketPrice":"1318.3150000000",
        //             "code":"FP",
        //             "avgPrice":"0",
        //             "execInst":null,
        //             "orderStatus":0,
        //             "userID":"1362494",
        //             "quote":"USDT",
        //             "rejectReason":null,
        //             "rejectCode":null,
        //             "$price":"500",
        //             "orderQty":"1",
        //             "commission":"0",
        //             "id":"268346885133053953",
        //             "$timeInForce":1,
        //             "isTriggered":false,
        //             "$side":1,
        //             "orderID":"1eOuPUAAkq",
        //             "leavesQty":"1",
        //             "cumQty":"0",
        //             "updateTime":null,
        //             "lastQty":null,
        //             "clOrdID":null,
        //             "$stopPrice":null,
        //             "createTime":null,
        //             "transactTime":null,
        //             "settleType":"VANILLA",
        //             "base":"ETH",
        //             "lastPrice":"0"
        //         ),
        //         "message":"success",
        //         "ts":1610250883059
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function edit_order($id, $symbol, $type, $side, $amount, $price = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'orderID' => $id,
            // 'orderQty' => $this->amount_to_precision($symbol, $amount),
            // 'price' => $this->price_to_precision($symbol, $price),
            // 'stopPrice' => $this->price_to_precision($symbol, $stopPrice),
        );
        $stopPrice = $this->safe_float($params, 'stopPrice');
        if ($stopPrice !== null) {
            $request['stopPrice'] = $this->price_to_precision($symbol, $stopPrice);
            $params = $this->omit($params, 'stopPrice');
        }
        if ($price !== null) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        if ($amount !== null) {
            $request['orderQty'] = $this->amount_to_precision($symbol, $amount);
        }
        $method = null;
        if ($market['spot']) {
            $method = 'privatePutSpotOrders';
        } else if ($market['futures']) {
            $method = 'privatePutFuturesOrders';
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "$symbol":"ETHUSDT",
        //             "orderType":2,
        //             "avgPrice":"0",
        //             "execInst":null,
        //             "orderStatus":0,
        //             "userID":"1362494",
        //             "quote":"USDT",
        //             "rejectReason":null,
        //             "rejectCode":null,
        //             "$price":"1500",
        //             "orderQty":"1",
        //             "commission":"0",
        //             "$id":"268323430253735936",
        //             "timeInForce":1,
        //             "isTriggered":false,
        //             "$side":2,
        //             "orderID":"1eO51MDSpQ",
        //             "leavesQty":"0",
        //             "cumQty":"0",
        //             "updateTime":null,
        //             "lastQty":"0",
        //             "clOrdID":null,
        //             "$stopPrice":null,
        //             "createTime":null,
        //             "transactTime":null,
        //             "base":"ETH",
        //             "lastPrice":"0"
        //         ),
        //         "message":"success",
        //         "ts":1610245290980
        //     }
        //
        // futures
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "liqType":0,
        //             "$symbol":"ETHUSDTFP",
        //             "orderType":2,
        //             "leverage":"1",
        //             "marketPrice":"1318.3150000000",
        //             "code":"FP",
        //             "avgPrice":"0",
        //             "execInst":null,
        //             "orderStatus":0,
        //             "userID":"1362494",
        //             "quote":"USDT",
        //             "rejectReason":null,
        //             "rejectCode":null,
        //             "$price":"500",
        //             "orderQty":"1",
        //             "commission":"0",
        //             "$id":"268346885133053953",
        //             "timeInForce":1,
        //             "isTriggered":false,
        //             "$side":1,
        //             "orderID":"1eOuPUAAkq",
        //             "leavesQty":"1",
        //             "cumQty":"0",
        //             "updateTime":null,
        //             "lastQty":null,
        //             "clOrdID":null,
        //             "$stopPrice":null,
        //             "createTime":null,
        //             "transactTime":null,
        //             "settleType":"VANILLA",
        //             "base":"ETH",
        //             "lastPrice":"0"
        //         ),
        //         "message":"success",
        //         "ts":1610250883059
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'orderID' => $id,
        );
        $method = null;
        $defaultType = $this->safe_string_2($this->options, 'cancelOrder', 'defaultType', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $params = $this->omit($params, 'type');
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $type = $market['type'];
        }
        if ($type === 'spot') {
            $method = 'privateDeleteSpotOrdersCancelOrderID';
        } else if ($type === 'futures') {
            $method = 'privateDeleteFuturesOrdersCancelOrderID';
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "avgPrice":"0",
        //             "base":"BTC",
        //             "clOrdID":"aax",
        //             "commission":"0",
        //             "createTime":"2019-11-12T03:46:41Z",
        //             "cumQty":"0",
        //             "$id":"114330021504606208",
        //             "isTriggered":false,
        //             "lastPrice":"0",
        //             "lastQty":"0",
        //             "leavesQty":"0",
        //             "orderID":"wJ4L366KB",
        //             "orderQty":"0.05",
        //             "orderStatus":1,
        //             "orderType":2,
        //             "price":"8000",
        //             "quote":"USDT",
        //             "rejectCode":0,
        //             "rejectReason":null,
        //             "side":1,
        //             "stopPrice":"0",
        //             "$symbol":"BTCUSDT",
        //             "transactTime":null,
        //             "updateTime":"2019-11-12T03:46:41Z",
        //             "timeInForce":1,
        //             "userID":"216214"
        //         ),
        //         "message":"success",
        //         "ts":1573530402029
        //     }
        //
        // futures
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "avgPrice":"0",
        //             "base":"BTC",
        //             "clOrdID":"aax_futures",
        //             "code":"FP",
        //             "commission":"0",
        //             "createTime":"2019-11-12T06:48:58Z",
        //             "cumQty":"0",
        //             "$id":"114375893764395008",
        //             "isTriggered":false,
        //             "lastPrice":"0",
        //             "lastQty":null,
        //             "leavesQty":"300",
        //             "leverage":"1",
        //             "liqType":0,
        //             "marketPrice":"8760.75",
        //             "orderID":"wJTewQc81",
        //             "orderQty":"300",
        //             "orderStatus":1,
        //             "orderType":2,
        //             "price":"8000",
        //             "quote":"USD",
        //             "rejectCode":0,
        //             "rejectReason":null,
        //             "settleType":"INVERSE",
        //             "side":1,
        //             "stopPrice":"0",
        //             "$symbol":"BTCUSDFP",
        //             "transactTime":"2019-11-12T06:48:58Z",
        //             "updateTime":"2019-11-12T06:48:58Z",
        //             "timeInForce":1,
        //             "execInst" => "",
        //             "userID":"216214"
        //         ),
        //         "message":"success",
        //         "ts":1573541642970
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $method = null;
        if ($market['spot']) {
            $method = 'privateDeleteSpotOrdersCancelAll';
        } else if ($market['futures']) {
            $method = 'privateDeleteFuturesOrdersCancelAll';
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        //     {
        //         "code":1,
        //         "data":array(
        //             "vBC9rXsEE",
        //             "vBCc46OI0"
        //             ),
        //         "message":"success",
        //         "ts":1572597435470
        //     }
        //
        return $response;
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        yield $this->load_markets();
        $defaultType = $this->safe_string_2($this->options, 'fetchOrder', 'defaultType', 'spot');
        $params['type'] = $this->safe_string($params, 'type', $defaultType);
        $request = array();
        $clientOrderId = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
        if ($clientOrderId === null) {
            $request['orderID'] = $id;
        } else {
            $request['clOrdID'] = $clientOrderId;
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
        }
        $orders = yield $this->fetch_orders($symbol, null, null, array_merge($request, $params));
        $order = $this->safe_value($orders, 0);
        if ($order === null) {
            if ($clientOrderId === null) {
                throw new OrderNotFound($this->id . ' fetchOrder() could not find $order $id ' . $id);
            } else {
                throw new OrderNotFound($this->id . ' fetchOrder() could not find $order clientOrderID ' . $clientOrderId);
            }
        }
        return $order;
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            // 'pageNum' => '1',
            // 'pageSize' => '10',
            // 'symbol' => $market['id'],
            // 'orderID' => id,
            // 'side' => 'null', // BUY, SELL
            // 'clOrdID' => $clientOrderId,
        );
        $defaultType = $this->safe_string_2($this->options, 'fetchOpenOrders', 'defaultType', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $params = $this->omit($params, 'type');
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            $type = $market['type'];
        }
        $clientOrderId = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['clOrdID'] = $clientOrderId;
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
        }
        $method = null;
        if ($type === 'spot') {
            $method = 'privateGetSpotOpenOrders';
        } else if ($type === 'futures') {
            $method = 'privateGetFuturesOpenOrders';
        }
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default 10
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code":1,
        //         "$data":{
        //             "total":19,
        //             "pageSize":10,
        //             "list":array(
        //                 array(
        //                     "orderType":2,
        //                     "$symbol":"BTCUSDT",
        //                     "avgPrice":"0",
        //                     "orderStatus":0,
        //                     "userID":"7225",
        //                     "quote":"USDT",
        //                     "rejectReason":null,
        //                     "rejectCode":null,
        //                     "price":"0",
        //                     "orderQty":"0.002",
        //                     "commission":"0",
        //                     "id":"110419975166304256",
        //                     "isTriggered":null,
        //                     "side":1,
        //                     "orderID":"vBGlDcLwk",
        //                     "cumQty":"0",
        //                     "leavesQty":"0",
        //                     "updateTime":null,
        //                     "clOrdID":"0001",
        //                     "lastQty":"0",
        //                     "stopPrice":"0",
        //                     "createTime":"2019-11-01T08:49:33Z",
        //                     "transactTime":null,
        //                     "timeInForce":1,
        //                     "base":"BTC",
        //                     "lastPrice":"0"
        //                 }
        //             ),
        //             "pageNum":1
        //         ),
        //         "message":"success",
        //         "ts":1572598173682
        //     }
        //
        // futures
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "list":array(
        //                 array(
        //                     "avgPrice":"8768.99999999484997",
        //                     "base":"BTC",
        //                     "clOrdID":null,
        //                     "code":"FP",
        //                     "commission":"0.00000913",
        //                     "createTime":"2019-11-12T07:05:52.000Z,
        //                     "cumQty":"100",
        //                     "id":"114380149603028993",
        //                     "isTriggered":false,
        //                     "lastPrice":"8769",
        //                     "lastQty":"100",
        //                     "leavesQty":"0",
        //                     "leverage":"1",
        //                     "liqType":1,
        //                     "marketPrice":"8769.75",
        //                     "orderID":"wJXURIFBT",
        //                     "orderQty":"100",
        //                     "orderStatus":3,
        //                     "orderType":1,
        //                     "price":"8769.75",
        //                     "quote":"USD",
        //                     "rejectCode":0,
        //                     "rejectReason":null,
        //                     "settleType":"INVERSE",
        //                     "side":2,
        //                     "stopPrice":"0",
        //                     "$symbol":"BTCUSDFP",
        //                     "transactTime":"2019-11-12T07:05:52.000Z,
        //                     "updateTime":"2019-11-12T07:05:52.000Z,
        //                     "timeInForce":1,
        //                     "execInst" => "",
        //                     "userID":"216214"
        //                 ),
        //             ),
        //             "pageNum":1,
        //             "pageSize":10,
        //             "total":21
        //         ),
        //         "message":"success",
        //         "ts":1573546960172
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $orders = $this->safe_value($data, 'list', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $request = array(
            'orderStatus' => '2', // 1 new, 2 filled, 3 canceled
        );
        return yield $this->fetch_orders($symbol, $since, $limit, array_merge($request, $params));
    }

    public function fetch_canceled_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $request = array(
            'orderStatus' => '3', // 1 new, 2 filled, 3 canceled
        );
        return yield $this->fetch_orders($symbol, $since, $limit, array_merge($request, $params));
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            // 'pageNum' => '1',
            // 'pageSize' => '10',
            // 'symbol' => $market['id'],
            // 'orderID' => id,
            // 'base' => $market['baseId'],
            // 'quote' => $market['quoteId'],
            // 'orderStatus' => null, // 1 new, 2 filled, 3 canceled
            // 'startDate' => $this->ymd($since),
            // 'endDate' => $this->ymd($this->milliseconds()),
            // 'orderType' => null, // MARKET, LIMIT, STOP, STOP-LIMIT
            // 'side' => 'null', // BUY, SELL
            // 'clOrdID' => $clientOrderId,
        );
        $method = null;
        $defaultType = $this->safe_string_2($this->options, 'fetchOrders', 'defaultType', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $params = $this->omit($params, 'type');
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            $type = $market['type'];
        }
        if ($type === 'spot') {
            $method = 'privateGetSpotOrders';
        } else if ($type === 'futures') {
            $method = 'privateGetFuturesOrders';
        }
        $clientOrderId = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['clOrdID'] = $clientOrderId;
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
        }
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default 10
        }
        if ($since !== null) {
            $request['startDate'] = $this->ymd($since);
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code":1,
        //         "$data":{
        //             "total":19,
        //             "pageSize":10,
        //             "list":array(
        //                 array(
        //                     "orderType":2,
        //                     "$symbol":"BTCUSDT",
        //                     "avgPrice":"0",
        //                     "orderStatus":0,
        //                     "userID":"7225",
        //                     "quote":"USDT",
        //                     "rejectReason":null,
        //                     "rejectCode":null,
        //                     "price":"0",
        //                     "orderQty":"0.002",
        //                     "commission":"0",
        //                     "id":"110419975166304256",
        //                     "isTriggered":null,
        //                     "side":1,
        //                     "orderID":"vBGlDcLwk",
        //                     "cumQty":"0",
        //                     "leavesQty":"0",
        //                     "updateTime":null,
        //                     "clOrdID":"0001",
        //                     "lastQty":"0",
        //                     "stopPrice":"0",
        //                     "createTime":"2019-11-01T08:49:33Z",
        //                     "transactTime":null,
        //                     "timeInForce":1,
        //                     "base":"BTC",
        //                     "lastPrice":"0"
        //                 }
        //             ),
        //             "pageNum":1
        //         ),
        //         "message":"success",
        //         "ts":1572598173682
        //     }
        //
        // futures
        //
        //     {
        //         "code":1,
        //         "$data":array(
        //             "list":array(
        //                 array(
        //                     "avgPrice":"8768.99999999484997",
        //                     "base":"BTC",
        //                     "clOrdID":null,
        //                     "code":"FP",
        //                     "commission":"0.00000913",
        //                     "createTime":"2019-11-12T07:05:52.000Z,
        //                     "cumQty":"100",
        //                     "id":"114380149603028993",
        //                     "isTriggered":false,
        //                     "lastPrice":"8769",
        //                     "lastQty":"100",
        //                     "leavesQty":"0",
        //                     "leverage":"1",
        //                     "liqType":1,
        //                     "marketPrice":"8769.75",
        //                     "orderID":"wJXURIFBT",
        //                     "orderQty":"100",
        //                     "orderStatus":3,
        //                     "orderType":1,
        //                     "price":"8769.75",
        //                     "quote":"USD",
        //                     "rejectCode":0,
        //                     "rejectReason":null,
        //                     "settleType":"INVERSE",
        //                     "side":2,
        //                     "stopPrice":"0",
        //                     "$symbol":"BTCUSDFP",
        //                     "transactTime":"2019-11-12T07:05:52.000Z,
        //                     "updateTime":"2019-11-12T07:05:52.000Z,
        //                     "timeInForce":1,
        //                     "execInst" => "",
        //                     "userID":"216214"
        //                 ),
        //             ),
        //             "pageNum":1,
        //             "pageSize":10,
        //             "total":21
        //         ),
        //         "message":"success",
        //         "ts":1573546960172
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $orders = $this->safe_value($data, 'list', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function parse_order_status($status) {
        $statuses = array(
            '0' => 'open', // pending new
            '1' => 'open', // new
            '2' => 'open', // partially-filled
            '3' => 'closed', // filled
            '4' => 'canceled', // cancel-reject
            '5' => 'canceled', // canceled
            '6' => 'rejected', // rejected
            '10' => 'expired', // expired
            '11' => 'rejected', // business-reject
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type($status) {
        $statuses = array(
            '1' => 'market',
            '2' => 'limit',
            '3' => 'stop',
            '4' => 'stop-limit',
            '7' => 'stop-loss',
            '8' => 'take-profit',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_time_in_force($timeInForce) {
        $timeInForces = array(
            '1' => 'GTC',
            '3' => 'IOC',
            '4' => 'FOK',
        );
        return $this->safe_string($timeInForces, $timeInForce, $timeInForce);
    }

    public function parse_order($order, $market = null) {
        //
        //     {
        //         "avgPrice":"8768.99999999484997",
        //         "base":"BTC",
        //         "clOrdID":null,
        //         "code":"FP", // futures only
        //         "commission":"0.00000913",
        //         "createTime":"2019-11-12T07:05:52.000Z,
        //         "cumQty":"100",
        //         "$id":"114380149603028993", // futures only
        //         "isTriggered":false,
        //         "lastPrice":"8769",
        //         "lastQty":"100",
        //         "leavesQty":"0",
        //         "leverage":"1", // futures only
        //         "liqType":1, // futures only
        //         "marketPrice":"8769.75", // futures only
        //         "orderID":"wJXURIFBT",
        //         "orderQty":"100",
        //         "orderStatus":3,
        //         "orderType":1,
        //         "$price":"8769.75",
        //         "quote":"USD",
        //         "rejectCode":0,
        //         "rejectReason":null,
        //         "settleType":"INVERSE", // futures only
        //         "$side":2,
        //         "$stopPrice":"0",
        //         "$symbol":"BTCUSDFP",
        //         "transactTime":"2019-11-12T07:05:52.000Z,
        //         "updateTime":"2019-11-12T07:05:52.000Z,
        //         "$timeInForce":1,
        //         "$execInst" => "",
        //         "userID":"216214"
        //     }
        //
        // sometimes the $timestamp is returned in milliseconds
        $timestamp = $this->safe_value($order, 'createTime');
        if (gettype($timestamp) === 'string') {
            $timestamp = $this->parse8601($timestamp);
        }
        $status = $this->parse_order_status($this->safe_string($order, 'orderStatus'));
        $type = $this->parse_order_type($this->safe_string($order, 'orderType'));
        $side = $this->safe_string($order, 'side');
        if ($side === '1') {
            $side = 'buy';
        } else if ($side === '2') {
            $side = 'sell';
        }
        $id = $this->safe_string($order, 'orderID');
        $clientOrderId = $this->safe_string($order, 'clOrdID');
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $price = $this->safe_float($order, 'price');
        $stopPrice = $this->safe_float($order, 'stopPrice');
        $timeInForce = $this->parse_time_in_force($this->safe_string($order, 'timeInForce'));
        $execInst = $this->safe_string($order, 'execInst');
        $postOnly = ($execInst === 'Post-Only');
        $average = $this->safe_float($order, 'avgPrice');
        $amount = $this->safe_float($order, 'orderQty');
        $filled = $this->safe_float($order, 'cumQty');
        $remaining = $this->safe_string($order, 'leavesQty');
        $cost = null;
        $lastTradeTimestamp = null;
        if ($filled !== null) {
            if ($price !== null) {
                $cost = $filled * $price;
            }
            if ($filled > 0) {
                $lastTradeTimestamp = $this->safe_value($order, 'transactTime');
                if (gettype($lastTradeTimestamp) === 'string') {
                    $lastTradeTimestamp = $this->parse8601($lastTradeTimestamp);
                }
            }
        }
        $fee = null;
        $feeCost = $this->safe_float($order, 'commission');
        if ($feeCost !== null) {
            $feeCurrency = null;
            if ($market !== null) {
                if ($side === 'buy') {
                    $feeCurrency = $market['base'];
                } else if ($side === 'sell') {
                    $feeCurrency = $market['quote'];
                }
            }
            $fee = array(
                'currency' => $feeCurrency,
                'cost' => $feeCost,
            );
        }
        return array(
            'id' => $id,
            'info' => $order,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'status' => $status,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'average' => $average,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'cost' => $cost,
            'trades' => null,
            'fee' => $fee,
        );
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            // 'pageNum' => '1',
            // 'pageSize' => '10',
            // 'symbol' => $market['id'],
            // 'orderID' => id,
            // 'base' => $market['baseId'],
            // 'quote' => $market['quoteId'],
            // 'startDate' => $this->ymd($since),
            // 'endDate' => $this->ymd($this->milliseconds()),
            // 'orderType' => null, // MARKET, LIMIT, STOP, STOP-LIMIT
            // 'side' => 'null', // BUY, SELL
        );
        $method = null;
        $defaultType = $this->safe_string_2($this->options, 'fetchMyTrades', 'defaultType', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $params = $this->omit($params, 'type');
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
            $type = $market['type'];
        }
        if ($type === 'spot') {
            $method = 'privateGetSpotTrades';
        } else if ($type === 'futures') {
            $method = 'privateGetFuturesTrades';
        }
        if ($limit !== null) {
            $request['pageSize'] = $limit; // default 10
        }
        if ($since !== null) {
            $request['startDate'] = $this->ymd($since);
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        //     {
        //         "code":1,
        //         "$data":{
        //             "list":array(
        //                 array(
        //                     "avgPrice":"1199.8",
        //                     "base":"ETH",
        //                     "clOrdID":null,
        //                     "commission":"0.00002",
        //                     "createTime":"2021-01-11T02:47:51.512Z",
        //                     "cumQty":"0.02",
        //                     "filledOrderID":"1eUD4F5rwK",
        //                     "filledPrice":"1199.8",
        //                     "filledQty":"0.02",
        //                     "leavesQty":"0",
        //                     "oCreateTime":"2021-01-11T02:47:51.377Z",
        //                     "orderID":"1eUD4EHfdU",
        //                     "orderQty":"0.02",
        //                     "orderStatus":3,
        //                     "orderType":1,
        //                     "price":"1198.25",
        //                     "quote":"USDT",
        //                     "rejectCode":null,
        //                     "rejectReason":null,
        //                     "side":1,
        //                     "stopPrice":"0",
        //                     "$symbol":"ETHUSDT",
        //                     "taker":true,
        //                     "tradeID":"E04WTIgfmULU",
        //                     "transactTime":"2021-01-11T02:47:51.389Z",
        //                     "updateTime":null,
        //                     "userID":"1362494"
        //                 }
        //             ),
        //             "pageNum":1,
        //             "pageSize":10,
        //             "total":1
        //         ),
        //         "message":"success",
        //         "ts":1610333278042
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $trades = $this->safe_value($data, 'list', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_deposit_address($code, $params = array ()) {
        yield $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
            // 'network' => null, // 'ERC20
        );
        $response = yield $this->privateGetAccountDepositAddress (array_merge($request, $params));
        //
        //     {
        //         "$code":1,
        //         "$data":array(
        //             "address":"0x080c5c667381404cca9be0be9a04b2e47691ff86",
        //             "tag":null,
        //             "$currency":"USDT",
        //             "network":"ERC20"
        //         ),
        //         "message":"success",
        //         "ts":1610270465132
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_deposit_address($data, $currency);
    }

    public function parse_deposit_address($depositAddress, $currency = null) {
        //
        //     {
        //         "$address":"0x080c5c667381404cca9be0be9a04b2e47691ff86",
        //         "$tag":null,
        //         "$currency":"USDT",
        //         "network":"ERC20"
        //     }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $tag = $this->safe_string($depositAddress, 'tag');
        $currencyId = $this->safe_string($depositAddress, 'currency');
        $code = $this->safe_currency_code($currencyId);
        return array(
            'info' => $depositAddress,
            'code' => $code,
            'address' => $address,
            'tag' => $tag,
        );
    }

    public function nonce() {
        return $this->milliseconds();
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        if ($api === 'v1') {
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } else {
            $url = '/' . $this->version . $url;
            if ($api === 'public') {
                if ($query) {
                    $url .= '?' . $this->urlencode($query);
                }
            } else if ($api === 'private') {
                $this->check_required_credentials();
                $nonce = (string) $this->nonce();
                $headers = array(
                    'X-ACCESS-KEY' => $this->apiKey,
                    'X-ACCESS-NONCE' => $nonce,
                );
                $auth = $nonce . ':' . $method;
                if ($method === 'GET') {
                    if ($query) {
                        $url .= '?' . $this->urlencode($query);
                    }
                    $auth .= $url;
                } else {
                    $headers['Content-Type'] = 'application/json';
                    $body = $this->json($query);
                    $auth .= $url . $body;
                }
                $signature = $this->hmac($this->encode($auth), $this->encode($this->secret));
                $headers['X-ACCESS-SIGN'] = $signature;
            }
        }
        $url = $this->implode_params($this->urls['api'][$api], array( 'hostname' => $this->hostname )) . $url;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors($code, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return; // fallback to default error handler
        }
        //
        //     array("$code":40102,"message":"Unauthorized(invalid key)")
        //
        $errorCode = $this->safe_string($response, 'code');
        if (($errorCode !== null) && ($errorCode !== '1')) {
            $feedback = $this->id . ' ' . $this->json($response);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
        }
    }
}
