<?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\ExchangeError;
use \ccxt\ArgumentsRequired;
use \ccxt\OrderNotFound;
use \ccxt\NotSupported;

class phemex extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'phemex',
            'name' => 'Phemex',
            'countries' => array( 'CN' ), // China
            'rateLimit' => 100,
            'version' => 'v1',
            'certified' => false,
            'pro' => true,
            'has' => array(
                'cancelAllOrders' => true, // swap contracts only
                'cancelOrder' => true,
                'createOrder' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchTicker' => true,
                'fetchTrades' => true,
                'fetchWithdrawals' => true,
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/85225056-221eb600-b3d7-11ea-930d-564d2690e3f6.jpg',
                'test' => array(
                    'v1' => 'https://testnet-api.phemex.com/v1',
                    'public' => 'https://testnet-api.phemex.com/exchange/public',
                    'private' => 'https://testnet-api.phemex.com',
                ),
                'api' => array(
                    'v1' => 'https://api.phemex.com/v1',
                    'public' => 'https://api.phemex.com/exchange/public',
                    'private' => 'https://api.phemex.com',
                ),
                'www' => 'https://phemex.com',
                'doc' => 'https://github.com/phemex/phemex-api-docs',
                'fees' => 'https://phemex.com/fees-conditions',
                'referral' => 'https://phemex.com/register?referralCode=EDNVJ',
            ),
            'timeframes' => array(
                '1m' => '60',
                '3m' => '180',
                '5m' => '300',
                '15m' => '900',
                '30m' => '1800',
                '1h' => '3600',
                '2h' => '7200',
                '3h' => '10800',
                '4h' => '14400',
                '6h' => '21600',
                '12h' => '43200',
                '1d' => '86400',
                '1w' => '604800',
                '1M' => '2592000',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'cfg/v2/products', // spot . contracts
                        'products', // contracts only
                        'nomics/trades', // ?market=<symbol>&since=<since>
                        'md/kline', // ?from=1589811875&resolution=1800&symbol=sBTCUSDT&to=1592457935
                    ),
                ),
                'v1' => array(
                    'get' => array(
                        'md/orderbook', // ?symbol=<symbol>&id=<id>
                        'md/trade', // ?symbol=<symbol>&id=<id>
                        'md/ticker/24hr', // ?symbol=<symbol>&id=<id>
                        'md/ticker/24hr/all', // ?id=<id>
                        'md/spot/ticker/24hr', // ?symbol=<symbol>&id=<id>
                        'md/spot/ticker/24hr/all', // ?symbol=<symbol>&id=<id>
                        'exchange/public/products', // contracts only
                    ),
                ),
                'private' => array(
                    'get' => array(
                        // spot
                        'spot/orders/active', // ?symbol=<symbol>&orderID=<orderID>
                        // 'spot/orders/active', // ?symbol=<symbol>&clOrDID=<clOrdID>
                        'spot/orders', // ?symbol=<symbol>
                        'spot/wallets', // ?currency=<currency>
                        'exchange/spot/order', // ?symbol=<symbol>&ordStatus=<ordStatus1,orderStatus2>ordType=<ordType1,orderType2>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        'exchange/spot/order/trades', // ?symbol=<symbol>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        // swap
                        'accounts/accountPositions', // ?currency=<currency>
                        'accounts/positions', // ?currency=<currency>
                        'orders/activeList', // ?symbol=<symbol>
                        'exchange/order/list', // ?symbol=<symbol>&start=<start>&end=<end>&offset=<offset>&limit=<limit>&ordStatus=<ordStatus>&withCount=<withCount>
                        'exchange/order', // ?symbol=<symbol>&orderID=<orderID1,orderID2>
                        // 'exchange/order', // ?symbol=<symbol>&clOrdID=<clOrdID1,clOrdID2>
                        'exchange/order/trade', // ?symbol=<symbol>&start=<start>&end=<end>&limit=<limit>&offset=<offset>&withCount=<withCount>
                        'phemex-user/users/children', // ?offset=<offset>&limit=<limit>&withCount=<withCount>
                        'phemex-user/wallets/v2/depositAddress', // ?_t=1592722635531&currency=USDT
                        'exchange/margins/transfer', // ?start=<start>&end=<end>&offset=<offset>&limit=<limit>&withCount=<withCount>
                        'exchange/wallets/confirm/withdraw', // ?code=<withdrawConfirmCode>
                        'exchange/wallets/withdrawList', // ?currency=<currency>&limit=<limit>&offset=<offset>&withCount=<withCount>
                        'exchange/wallets/depositList', // ?currency=<currency>&offset=<offset>&limit=<limit>
                        'exchange/wallets/v2/depositAddress', // ?currency=<currency>
                    ),
                    'post' => array(
                        // spot
                        'spot/orders',
                        // swap
                        'orders',
                        'positions/assign', // ?symbol=<symbol>&posBalance=<posBalance>&posBalanceEv=<posBalanceEv>
                        'exchange/wallets/transferOut',
                        'exchange/wallets/transferIn',
                        'exchange/margins',
                        'exchange/wallets/createWithdraw', // ?otpCode=<otpCode>
                        'exchange/wallets/cancelWithdraw',
                        'exchange/wallets/createWithdrawAddress', // ?otpCode={optCode}
                    ),
                    'put' => array(
                        // spot
                        'spot/orders', // ?symbol=<symbol>&orderID=<orderID>&origClOrdID=<origClOrdID>&clOrdID=<clOrdID>&priceEp=<priceEp>&baseQtyEV=<baseQtyEV>&quoteQtyEv=<quoteQtyEv>&stopPxEp=<stopPxEp>
                        // swap
                        'orders/replace', // ?symbol=<symbol>&orderID=<orderID>&origClOrdID=<origClOrdID>&clOrdID=<clOrdID>&price=<price>&priceEp=<priceEp>&orderQty=<orderQty>&stopPx=<stopPx>&stopPxEp=<stopPxEp>&takeProfit=<takeProfit>&takeProfitEp=<takeProfitEp>&stopLoss=<stopLoss>&stopLossEp=<stopLossEp>&pegOffsetValueEp=<pegOffsetValueEp>&pegPriceType=<pegPriceType>
                        'positions/leverage', // ?symbol=<symbol>&leverage=<leverage>&leverageEr=<leverageEr>
                        'positions/riskLimit', // ?symbol=<symbol>&riskLimit=<riskLimit>&riskLimitEv=<riskLimitEv>
                    ),
                    'delete' => array(
                        // spot
                        'spot/orders', // ?symbol=<symbol>&orderID=<orderID>
                        // 'spot/orders', // ?symbol=<symbol>&clOrdID=<clOrdID>
                        // swap
                        'orders/cancel', // ?symbol=<symbol>&orderID=<orderID>
                        'orders', // ?symbol=<symbol>&orderID=<orderID1>,<orderID2>,<orderID3>
                        'orders/all', // ?symbol=<symbol>&untriggered=<untriggered>&text=<text>
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'taker' => 0.1 / 100,
                    'maker' => 0.1 / 100,
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
            ),
            'exceptions' => array(
                'exact' => array(
                    // not documented
                    '412' => '\\ccxt\\BadRequest', // array("code":412,"msg":"Missing parameter - resolution","data":null)
                    '6001' => '\\ccxt\\BadRequest', // array("error":array("code":6001,"message":"invalid argument"),"id":null,"result":null)
                    // documented
                    '19999' => '\\ccxt\\BadRequest', // REQUEST_IS_DUPLICATED Duplicated request ID
                    '10001' => '\\ccxt\\DuplicateOrderId', // OM_DUPLICATE_ORDERID Duplicated order ID
                    '10002' => '\\ccxt\\OrderNotFound', // OM_ORDER_NOT_FOUND Cannot find order ID
                    '10003' => '\\ccxt\\CancelPending', // OM_ORDER_PENDING_CANCEL Cannot cancel while order is already in pending cancel status
                    '10004' => '\\ccxt\\CancelPending', // OM_ORDER_PENDING_REPLACE Cannot cancel while order is already in pending cancel status
                    '10005' => '\\ccxt\\CancelPending', // OM_ORDER_PENDING Cannot cancel while order is already in pending cancel status
                    '11001' => '\\ccxt\\InsufficientFunds', // TE_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance
                    '11002' => '\\ccxt\\InvalidOrder', // TE_INVALID_RISK_LIMIT Invalid risk limit value
                    '11003' => '\\ccxt\\InsufficientFunds', // TE_NO_ENOUGH_BALANCE_FOR_NEW_RISK_LIMIT Insufficient available balance
                    '11004' => '\\ccxt\\InvalidOrder', // TE_INVALID_LEVERAGE invalid input or new leverage is over maximum allowed leverage
                    '11005' => '\\ccxt\\InsufficientFunds', // TE_NO_ENOUGH_BALANCE_FOR_NEW_LEVERAGE Insufficient available balance
                    '11006' => '\\ccxt\\ExchangeError', // TE_CANNOT_CHANGE_POSITION_MARGIN_WITHOUT_POSITION Position size is zero. Cannot change margin
                    '11007' => '\\ccxt\\ExchangeError', // TE_CANNOT_CHANGE_POSITION_MARGIN_FOR_CROSS_MARGIN Cannot change margin under CrossMargin
                    '11008' => '\\ccxt\\ExchangeError', // TE_CANNOT_REMOVE_POSITION_MARGIN_MORE_THAN_ADDED exceeds the maximum removable Margin
                    '11009' => '\\ccxt\\ExchangeError', // TE_CANNOT_REMOVE_POSITION_MARGIN_DUE_TO_UNREALIZED_PNL exceeds the maximum removable Margin
                    '11010' => '\\ccxt\\InsufficientFunds', // TE_CANNOT_ADD_POSITION_MARGIN_DUE_TO_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance
                    '11011' => '\\ccxt\\InvalidOrder', // TE_REDUCE_ONLY_ABORT Cannot accept reduce only order
                    '11012' => '\\ccxt\\InvalidOrder', // TE_REPLACE_TO_INVALID_QTY Order quantity Error
                    '11013' => '\\ccxt\\InvalidOrder', // TE_CONDITIONAL_NO_POSITION Position size is zero. Cannot determine conditional order's quantity
                    '11014' => '\\ccxt\\InvalidOrder', // TE_CONDITIONAL_CLOSE_POSITION_WRONG_SIDE Close position conditional order has the same side
                    '11015' => '\\ccxt\\InvalidOrder', // TE_CONDITIONAL_TRIGGERED_OR_CANCELED
                    '11016' => '\\ccxt\\BadRequest', // TE_ADL_NOT_TRADING_REQUESTED_ACCOUNT Request is routed to the wrong trading engine
                    '11017' => '\\ccxt\\ExchangeError', // TE_ADL_CANNOT_FIND_POSITION Cannot find requested position on current account
                    '11018' => '\\ccxt\\ExchangeError', // TE_NO_NEED_TO_SETTLE_FUNDING The current account does not need to pay a funding fee
                    '11019' => '\\ccxt\\ExchangeError', // TE_FUNDING_ALREADY_SETTLED The current account already pays the funding fee
                    '11020' => '\\ccxt\\ExchangeError', // TE_CANNOT_TRANSFER_OUT_DUE_TO_BONUS Withdraw to wallet needs to remove all remaining bonus. However if bonus is used by position or order cost, withdraw fails.
                    '11021' => '\\ccxt\\ExchangeError', // TE_INVALID_BONOUS_AMOUNT // Grpc command cannot be negative number Invalid bonus amount
                    '11022' => '\\ccxt\\AccountSuspended', // TE_REJECT_DUE_TO_BANNED Account is banned
                    '11023' => '\\ccxt\\ExchangeError', // TE_REJECT_DUE_TO_IN_PROCESS_OF_LIQ Account is in the process of liquidation
                    '11024' => '\\ccxt\\ExchangeError', // TE_REJECT_DUE_TO_IN_PROCESS_OF_ADL Account is in the process of auto-deleverage
                    '11025' => '\\ccxt\\BadRequest', // TE_ROUTE_ERROR Request is routed to the wrong trading engine
                    '11026' => '\\ccxt\\ExchangeError', // TE_UID_ACCOUNT_MISMATCH
                    '11027' => '\\ccxt\\BadSymbol', // TE_SYMBOL_INVALID Invalid number ID or name
                    '11028' => '\\ccxt\\BadSymbol', // TE_CURRENCY_INVALID Invalid currency ID or name
                    '11029' => '\\ccxt\\ExchangeError', // TE_ACTION_INVALID Unrecognized request type
                    '11030' => '\\ccxt\\ExchangeError', // TE_ACTION_BY_INVALID
                    '11031' => '\\ccxt\\DDoSProtection', // TE_SO_NUM_EXCEEDS Number of total conditional orders exceeds the max limit
                    '11032' => '\\ccxt\\DDoSProtection', // TE_AO_NUM_EXCEEDS Number of total active orders exceeds the max limit
                    '11033' => '\\ccxt\\DuplicateOrderId', // TE_ORDER_ID_DUPLICATE Duplicated order ID
                    '11034' => '\\ccxt\\InvalidOrder', // TE_SIDE_INVALID Invalid side
                    '11035' => '\\ccxt\\InvalidOrder', // TE_ORD_TYPE_INVALID Invalid OrderType
                    '11036' => '\\ccxt\\InvalidOrder', // TE_TIME_IN_FORCE_INVALID Invalid TimeInForce
                    '11037' => '\\ccxt\\InvalidOrder', // TE_EXEC_INST_INVALID Invalid ExecType
                    '11038' => '\\ccxt\\InvalidOrder', // TE_TRIGGER_INVALID Invalid trigger type
                    '11039' => '\\ccxt\\InvalidOrder', // TE_STOP_DIRECTION_INVALID Invalid stop direction type
                    '11040' => '\\ccxt\\InvalidOrder', // TE_NO_MARK_PRICE Cannot get valid mark price to create conditional order
                    '11041' => '\\ccxt\\InvalidOrder', // TE_NO_INDEX_PRICE Cannot get valid index price to create conditional order
                    '11042' => '\\ccxt\\InvalidOrder', // TE_NO_LAST_PRICE Cannot get valid last market price to create conditional order
                    '11043' => '\\ccxt\\InvalidOrder', // TE_RISING_TRIGGER_DIRECTLY Conditional order would be triggered immediately
                    '11044' => '\\ccxt\\InvalidOrder', // TE_FALLING_TRIGGER_DIRECTLY Conditional order would be triggered immediately
                    '11045' => '\\ccxt\\InvalidOrder', // TE_TRIGGER_PRICE_TOO_LARGE Conditional order trigger price is too high
                    '11046' => '\\ccxt\\InvalidOrder', // TE_TRIGGER_PRICE_TOO_SMALL Conditional order trigger price is too low
                    '11047' => '\\ccxt\\InvalidOrder', // TE_BUY_TP_SHOULD_GT_BASE TakeProfile BUY conditional order trigger price needs to be greater than reference price
                    '11048' => '\\ccxt\\InvalidOrder', // TE_BUY_SL_SHOULD_LT_BASE StopLoss BUY condition order price needs to be less than the reference price
                    '11049' => '\\ccxt\\InvalidOrder', // TE_BUY_SL_SHOULD_GT_LIQ StopLoss BUY condition order price needs to be greater than liquidation price or it will not trigger
                    '11050' => '\\ccxt\\InvalidOrder', // TE_SELL_TP_SHOULD_LT_BASE TakeProfile SELL conditional order trigger price needs to be less than reference price
                    '11051' => '\\ccxt\\InvalidOrder', // TE_SELL_SL_SHOULD_LT_LIQ StopLoss SELL condition order price needs to be less than liquidation price or it will not trigger
                    '11052' => '\\ccxt\\InvalidOrder', // TE_SELL_SL_SHOULD_GT_BASE StopLoss SELL condition order price needs to be greater than the reference price
                    '11053' => '\\ccxt\\InvalidOrder', // TE_PRICE_TOO_LARGE
                    '11054' => '\\ccxt\\InvalidOrder', // TE_PRICE_WORSE_THAN_BANKRUPT Order price cannot be more aggressive than bankrupt price if this order has instruction to close a position
                    '11055' => '\\ccxt\\InvalidOrder', // TE_PRICE_TOO_SMALL Order price is too low
                    '11056' => '\\ccxt\\InvalidOrder', // TE_QTY_TOO_LARGE Order quantity is too large
                    '11057' => '\\ccxt\\InvalidOrder', // TE_QTY_NOT_MATCH_REDUCE_ONLY Does not allow ReduceOnly order without position
                    '11058' => '\\ccxt\\InvalidOrder', // TE_QTY_TOO_SMALL Order quantity is too small
                    '11059' => '\\ccxt\\InvalidOrder', // TE_TP_SL_QTY_NOT_MATCH_POS Position size is zero. Cannot accept any TakeProfit or StopLoss order
                    '11060' => '\\ccxt\\InvalidOrder', // TE_SIDE_NOT_CLOSE_POS TakeProfit or StopLoss order has wrong side. Cannot close position
                    '11061' => '\\ccxt\\CancelPending', // TE_ORD_ALREADY_PENDING_CANCEL Repeated cancel request
                    '11062' => '\\ccxt\\InvalidOrder', // TE_ORD_ALREADY_CANCELED Order is already canceled
                    '11063' => '\\ccxt\\InvalidOrder', // TE_ORD_STATUS_CANNOT_CANCEL Order is not able to be canceled under current status
                    '11064' => '\\ccxt\\InvalidOrder', // TE_ORD_ALREADY_PENDING_REPLACE Replace request is rejected because order is already in pending replace status
                    '11065' => '\\ccxt\\InvalidOrder', // TE_ORD_REPLACE_NOT_MODIFIED Replace request does not modify any parameters of the order
                    '11066' => '\\ccxt\\InvalidOrder', // TE_ORD_STATUS_CANNOT_REPLACE Order is not able to be replaced under current status
                    '11067' => '\\ccxt\\InvalidOrder', // TE_CANNOT_REPLACE_PRICE Market conditional order cannot change price
                    '11068' => '\\ccxt\\InvalidOrder', // TE_CANNOT_REPLACE_QTY Condtional order for closing position cannot change order quantity, since the order quantity is determined by position size already
                    '11069' => '\\ccxt\\ExchangeError', // TE_ACCOUNT_NOT_IN_RANGE The account ID in the request is not valid or is not in the range of the current process
                    '11070' => '\\ccxt\\BadSymbol', // TE_SYMBOL_NOT_IN_RANGE The symbol is invalid
                    '11071' => '\\ccxt\\InvalidOrder', // TE_ORD_STATUS_CANNOT_TRIGGER
                    '11072' => '\\ccxt\\InvalidOrder', // TE_TKFR_NOT_IN_RANGE The fee value is not valid
                    '11073' => '\\ccxt\\InvalidOrder', // TE_MKFR_NOT_IN_RANGE The fee value is not valid
                    '11074' => '\\ccxt\\InvalidOrder', // TE_CANNOT_ATTACH_TP_SL Order request cannot contain TP/SL parameters when the account already has positions
                    '11075' => '\\ccxt\\InvalidOrder', // TE_TP_TOO_LARGE TakeProfit price is too large
                    '11076' => '\\ccxt\\InvalidOrder', // TE_TP_TOO_SMALL TakeProfit price is too small
                    '11077' => '\\ccxt\\InvalidOrder', // TE_TP_TRIGGER_INVALID Invalid trigger type
                    '11078' => '\\ccxt\\InvalidOrder', // TE_SL_TOO_LARGE StopLoss price is too large
                    '11079' => '\\ccxt\\InvalidOrder', // TE_SL_TOO_SMALL StopLoss price is too small
                    '11080' => '\\ccxt\\InvalidOrder', // TE_SL_TRIGGER_INVALID Invalid trigger type
                    '11081' => '\\ccxt\\InvalidOrder', // TE_RISK_LIMIT_EXCEEDS Total potential position breaches current risk limit
                    '11082' => '\\ccxt\\InsufficientFunds', // TE_CANNOT_COVER_ESTIMATE_ORDER_LOSS The remaining balance cannot cover the potential unrealized PnL for this new order
                    '11083' => '\\ccxt\\InvalidOrder', // TE_TAKE_PROFIT_ORDER_DUPLICATED TakeProfit order already exists
                    '11084' => '\\ccxt\\InvalidOrder', // TE_STOP_LOSS_ORDER_DUPLICATED StopLoss order already exists
                    '11085' => '\\ccxt\\DuplicateOrderId', // TE_CL_ORD_ID_DUPLICATE ClOrdId is duplicated
                    '11086' => '\\ccxt\\InvalidOrder', // TE_PEG_PRICE_TYPE_INVALID PegPriceType is invalid
                    '11087' => '\\ccxt\\InvalidOrder', // TE_BUY_TS_SHOULD_LT_BASE The trailing order's StopPrice should be less than the current last price
                    '11088' => '\\ccxt\\InvalidOrder', // TE_BUY_TS_SHOULD_GT_LIQ The traling order's StopPrice should be greater than the current liquidation price
                    '11089' => '\\ccxt\\InvalidOrder', // TE_SELL_TS_SHOULD_LT_LIQ The traling order's StopPrice should be greater than the current last price
                    '11090' => '\\ccxt\\InvalidOrder', // TE_SELL_TS_SHOULD_GT_BASE The traling order's StopPrice should be less than the current liquidation price
                    '11091' => '\\ccxt\\InvalidOrder', // TE_BUY_REVERT_VALUE_SHOULD_LT_ZERO The PegOffset should be less than zero
                    '11092' => '\\ccxt\\InvalidOrder', // TE_SELL_REVERT_VALUE_SHOULD_GT_ZERO The PegOffset should be greater than zero
                    '11093' => '\\ccxt\\InvalidOrder', // TE_BUY_TTP_SHOULD_ACTIVATE_ABOVE_BASE The activation price should be greater than the current last price
                    '11094' => '\\ccxt\\InvalidOrder', // TE_SELL_TTP_SHOULD_ACTIVATE_BELOW_BASE The activation price should be less than the current last price
                    '11095' => '\\ccxt\\InvalidOrder', // TE_TRAILING_ORDER_DUPLICATED A trailing order exists already
                    '11096' => '\\ccxt\\InvalidOrder', // TE_CLOSE_ORDER_CANNOT_ATTACH_TP_SL An order to close position cannot have trailing instruction
                    '11097' => '\\ccxt\\BadRequest', // TE_CANNOT_FIND_WALLET_OF_THIS_CURRENCY This crypto is not supported
                    '11098' => '\\ccxt\\BadRequest', // TE_WALLET_INVALID_ACTION Invalid action on wallet
                    '11099' => '\\ccxt\\ExchangeError', // TE_WALLET_VID_UNMATCHED Wallet operation request has a wrong wallet vid
                    '11100' => '\\ccxt\\InsufficientFunds', // TE_WALLET_INSUFFICIENT_BALANCE Wallet has insufficient balance
                    '11101' => '\\ccxt\\InsufficientFunds', // TE_WALLET_INSUFFICIENT_LOCKED_BALANCE Locked balance in wallet is not enough for unlock/withdraw request
                    '11102' => '\\ccxt\\BadRequest', // TE_WALLET_INVALID_DEPOSIT_AMOUNT Deposit amount must be greater than zero
                    '11103' => '\\ccxt\\BadRequest', // TE_WALLET_INVALID_WITHDRAW_AMOUNT Withdraw amount must be less than zero
                    '11104' => '\\ccxt\\BadRequest', // TE_WALLET_REACHED_MAX_AMOUNT Deposit makes wallet exceed max amount allowed
                    '11105' => '\\ccxt\\InsufficientFunds', // TE_PLACE_ORDER_INSUFFICIENT_BASE_BALANCE Insufficient funds in base wallet
                    '11106' => '\\ccxt\\InsufficientFunds', // TE_PLACE_ORDER_INSUFFICIENT_QUOTE_BALANCE Insufficient funds in quote wallet
                    '11107' => '\\ccxt\\ExchangeError', // TE_CANNOT_CONNECT_TO_REQUEST_SEQ TradingEngine failed to connect with CrossEngine
                    '11108' => '\\ccxt\\InvalidOrder', // TE_CANNOT_REPLACE_OR_CANCEL_MARKET_ORDER Cannot replace/amend market order
                    '11109' => '\\ccxt\\InvalidOrder', // TE_CANNOT_REPLACE_OR_CANCEL_IOC_ORDER Cannot replace/amend ImmediateOrCancel order
                    '11110' => '\\ccxt\\InvalidOrder', // TE_CANNOT_REPLACE_OR_CANCEL_FOK_ORDER Cannot replace/amend FillOrKill order
                    '11111' => '\\ccxt\\InvalidOrder', // TE_MISSING_ORDER_ID OrderId is missing
                    '11112' => '\\ccxt\\InvalidOrder', // TE_QTY_TYPE_INVALID QtyType is invalid
                    '11113' => '\\ccxt\\BadRequest', // TE_USER_ID_INVALID UserId is invalid
                    '11114' => '\\ccxt\\InvalidOrder', // TE_ORDER_VALUE_TOO_LARGE Order value is too large
                    '11115' => '\\ccxt\\InvalidOrder', // TE_ORDER_VALUE_TOO_SMALL Order value is too small
                    // not documented
                    '30018' => '\\ccxt\\BadRequest', // array("code":30018,"msg":"phemex.data.size.uplimt","data":null)
                    '39996' => '\\ccxt\\PermissionDenied', // array("code" => "39996","msg" => "Access denied.")
                ),
                'broad' => array(
                    'Failed to find api-key' => '\\ccxt\\AuthenticationError', // array("msg":"Failed to find api-key 1c5ec63fd-660d-43ea-847a-0d3ba69e106e","code":10500)
                    'Missing required parameter' => '\\ccxt\\BadRequest', // array("msg":"Missing required parameter","code":10500)
                    'API Signature verification failed' => '\\ccxt\\AuthenticationError', // array("msg":"API Signature verification failed.","code":10500)
                ),
            ),
            'options' => array(
                'x-phemex-request-expiry' => 60, // in seconds
                'createOrderByQuoteRequiresPrice' => true,
            ),
        ));
    }

    public function parse_safe_float($value = null) {
        if ($value === null) {
            return $value;
        }
        $value = str_replace(',', '', $value);
        $parts = explode(' ', $value);
        return $this->safe_float($parts, 0);
    }

    public function parse_swap_market($market) {
        //
        //     {
        //         "$symbol":"BTCUSD",
        //         "displaySymbol":"BTC / USD",
        //         "indexSymbol":".BTC",
        //         "markSymbol":".MBTC",
        //         "fundingRateSymbol":".BTCFR",
        //         "fundingRate8hSymbol":".BTCFR8H",
        //         "contractUnderlyingAssets":"USD",
        //         "settleCurrency":"BTC",
        //         "quoteCurrency":"USD",
        //         "contractSize":"1 USD",
        //         "lotSize":1,
        //         "tickSize":0.5,
        //         "$priceScale":4,
        //         "$ratioScale":8,
        //         "pricePrecision":1,
        //         "$minPriceEp":5000,
        //         "$maxPriceEp":10000000000,
        //         "maxOrderQty":1000000,
        //         "$type":"Perpetual"
        //         "steps":"50",
        //         "riskLimits":array(
        //             array("limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000),
        //             array("limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000),
        //             array("limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000),
        //         ),
        //         "underlyingSymbol":".BTC",
        //         "baseCurrency":"BTC",
        //         "settlementCurrency":"BTC",
        //         "$valueScale":8,
        //         "defaultLeverage":0,
        //         "maxLeverage":100,
        //         "initMarginEr":"1000000",
        //         "maintMarginEr":"500000",
        //         "defaultRiskLimitEv":10000000000,
        //         "deleverage":true,
        //         "$makerFeeRateEr":-250000,
        //         "$takerFeeRateEr":750000,
        //         "fundingInterval":8,
        //         "marketUrl":"https://phemex.com/trade/BTCUSD",
        //         "description":"BTCUSD is a BTC/USD perpetual contract priced on the .BTC Index. Each contract is worth 1 USD of Bitcoin. Funding is paid and received every 8 hours. At UTC time => 00:00, 08:00, 16:00.",
        //     }
        //
        $id = $this->safe_string($market, 'symbol');
        $baseId = $this->safe_string($market, 'baseCurrency', 'contractUnderlyingAssets');
        $quoteId = $this->safe_string($market, 'quoteCurrency');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $type = $this->safe_string_lower($market, 'type');
        $taker = null;
        $maker = null;
        $inverse = false;
        $spot = false;
        $swap = true;
        $settlementCurrencyId = $this->safe_string($market, 'settlementCurrency');
        if ($settlementCurrencyId !== $quoteId) {
            $inverse = true;
        }
        $linear = !$inverse;
        $precision = array(
            'amount' => $this->safe_float($market, 'lotSize'),
            'price' => $this->safe_float($market, 'tickSize'),
        );
        $priceScale = $this->safe_integer($market, 'priceScale');
        $ratioScale = $this->safe_integer($market, 'ratioScale');
        $valueScale = $this->safe_integer($market, 'valueScale');
        $minPriceEp = $this->safe_float($market, 'minPriceEp');
        $maxPriceEp = $this->safe_float($market, 'maxPriceEp');
        $makerFeeRateEr = $this->safe_float($market, 'makerFeeRateEr');
        $takerFeeRateEr = $this->safe_float($market, 'takerFeeRateEr');
        if ($makerFeeRateEr !== null) {
            $maker = $this->from_en($makerFeeRateEr, $ratioScale, 0.00000001);
        }
        if ($takerFeeRateEr !== null) {
            $taker = $this->from_en($takerFeeRateEr, $ratioScale, 0.00000001);
        }
        $limits = array(
            'amount' => array(
                'min' => $precision['amount'],
                'max' => null,
            ),
            'price' => array(
                'min' => $this->from_en($minPriceEp, $priceScale, $precision['price']),
                'max' => $this->from_en($maxPriceEp, $priceScale, $precision['price']),
            ),
            'cost' => array(
                'min' => null,
                'max' => $this->parse_safe_float($this->safe_string($market, 'maxOrderQty')),
            ),
        );
        $active = null;
        return array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'info' => $market,
            'type' => $type,
            'spot' => $spot,
            'swap' => $swap,
            'linear' => $linear,
            'inverse' => $inverse,
            'active' => $active,
            'taker' => $taker,
            'maker' => $maker,
            'priceScale' => $priceScale,
            'valueScale' => $valueScale,
            'ratioScale' => $ratioScale,
            'precision' => $precision,
            'limits' => $limits,
        );
    }

    public function parse_spot_market($market) {
        //
        //     {
        //         "$symbol":"sBTCUSDT",
        //         "displaySymbol":"BTC / USDT",
        //         "quoteCurrency":"USDT",
        //         "pricePrecision":2,
        //         "$type":"Spot",
        //         "baseCurrency":"BTC",
        //         "baseTickSize":"0.000001 BTC",
        //         "baseTickSizeEv":100,
        //         "quoteTickSize":"0.01 USDT",
        //         "quoteTickSizeEv":1000000,
        //         "minOrderValue":"10 USDT",
        //         "minOrderValueEv":1000000000,
        //         "maxBaseOrderSize":"1000 BTC",
        //         "maxBaseOrderSizeEv":100000000000,
        //         "maxOrderValue":"5,000,000 USDT",
        //         "maxOrderValueEv":500000000000000,
        //         "defaultTakerFee":"0.001",
        //         "defaultTakerFeeEr":100000,
        //         "defaultMakerFee":"0.001",
        //         "defaultMakerFeeEr":100000,
        //         "baseQtyPrecision":6,
        //         "quoteQtyPrecision":2
        //     }
        //
        $type = $this->safe_string_lower($market, 'type');
        $id = $this->safe_string($market, 'symbol');
        $quoteId = $this->safe_string($market, 'quoteCurrency');
        $baseId = $this->safe_string($market, 'baseCurrency');
        $linear = null;
        $inverse = null;
        $spot = true;
        $swap = false;
        $taker = $this->safe_float($market, 'defaultTakerFee');
        $maker = $this->safe_float($market, 'defaultMakerFee');
        $precision = array(
            'amount' => $this->parse_safe_float($this->safe_string($market, 'baseTickSize')),
            'price' => $this->parse_safe_float($this->safe_string($market, 'quoteTickSize')),
        );
        $limits = array(
            'amount' => array(
                'min' => $precision['amount'],
                'max' => $this->parse_safe_float($this->safe_string($market, 'maxBaseOrderSize')),
            ),
            'price' => array(
                'min' => $precision['price'],
                'max' => null,
            ),
            'cost' => array(
                'min' => $this->parse_safe_float($this->safe_string($market, 'minOrderValue')),
                'max' => $this->parse_safe_float($this->safe_string($market, 'maxOrderValue')),
            ),
        );
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $active = null;
        return array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'info' => $market,
            'type' => $type,
            'spot' => $spot,
            'swap' => $swap,
            'linear' => $linear,
            'inverse' => $inverse,
            'active' => $active,
            'taker' => $taker,
            'maker' => $maker,
            'precision' => $precision,
            'priceScale' => 8,
            'valueScale' => 8,
            'ratioScale' => 8,
            'limits' => $limits,
        );
    }

    public function fetch_markets($params = array ()) {
        $v2Products = yield $this->publicGetCfgV2Products ($params);
        //
        //     {
        //         "code":0,
        //         "msg":"OK",
        //         "data":{
        //             "ratioScale":8,
        //             "currencies":array(
        //                 array("currency":"BTC","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"Bitcoin"),
        //                 array("currency":"USD","valueScale":4,"minValueEv":1,"maxValueEv":500000000000000,"name":"USD"),
        //                 array("currency":"USDT","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"TetherUS"),
        //             ),
        //             "$products":array(
        //                 array(
        //                     "symbol":"BTCUSD",
        //                     "displaySymbol":"BTC / USD",
        //                     "indexSymbol":".BTC",
        //                     "markSymbol":".MBTC",
        //                     "fundingRateSymbol":".BTCFR",
        //                     "fundingRate8hSymbol":".BTCFR8H",
        //                     "contractUnderlyingAssets":"USD",
        //                     "settleCurrency":"BTC",
        //                     "quoteCurrency":"USD",
        //                     "contractSize":1.0,
        //                     "lotSize":1,
        //                     "tickSize":0.5,
        //                     "priceScale":4,
        //                     "ratioScale":8,
        //                     "pricePrecision":1,
        //                     "minPriceEp":5000,
        //                     "maxPriceEp":10000000000,
        //                     "maxOrderQty":1000000,
        //                     "$type":"Perpetual"
        //                 ),
        //                 array(
        //                     "symbol":"sBTCUSDT",
        //                     "displaySymbol":"BTC / USDT",
        //                     "quoteCurrency":"USDT",
        //                     "pricePrecision":2,
        //                     "$type":"Spot",
        //                     "baseCurrency":"BTC",
        //                     "baseTickSize":"0.000001 BTC",
        //                     "baseTickSizeEv":100,
        //                     "quoteTickSize":"0.01 USDT",
        //                     "quoteTickSizeEv":1000000,
        //                     "minOrderValue":"10 USDT",
        //                     "minOrderValueEv":1000000000,
        //                     "maxBaseOrderSize":"1000 BTC",
        //                     "maxBaseOrderSizeEv":100000000000,
        //                     "maxOrderValue":"5,000,000 USDT",
        //                     "maxOrderValueEv":500000000000000,
        //                     "defaultTakerFee":"0.001",
        //                     "defaultTakerFeeEr":100000,
        //                     "defaultMakerFee":"0.001",
        //                     "defaultMakerFeeEr":100000,
        //                     "baseQtyPrecision":6,
        //                     "quoteQtyPrecision":2
        //                 ),
        //             ),
        //             "$riskLimits":array(
        //                 array(
        //                     "symbol":"BTCUSD",
        //                     "steps":"50",
        //                     "$riskLimits":array(
        //                         array("limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000),
        //                         array("limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000),
        //                         array("limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000),
        //                     )
        //                 ),
        //             ),
        //             "leverages":[
        //                 array("initialMargin":"1.0%","initialMarginEr":1000000,"options":[1,2,3,5,10,25,50,100]),
        //                 array("initialMargin":"1.5%","initialMarginEr":1500000,"options":[1,2,3,5,10,25,50,66]),
        //                 array("initialMargin":"2.0%","initialMarginEr":2000000,"options":[1,2,3,5,10,25,33,50]),
        //             ]
        //         }
        //     }
        //
        $v1Products = yield $this->v1GetExchangePublicProducts ($params);
        $v1ProductsData = $this->safe_value($v1Products, 'data', array());
        //
        //     {
        //         "code":0,
        //         "msg":"OK",
        //         "data":array(
        //             array(
        //                 "symbol":"BTCUSD",
        //                 "underlyingSymbol":".BTC",
        //                 "quoteCurrency":"USD",
        //                 "baseCurrency":"BTC",
        //                 "settlementCurrency":"BTC",
        //                 "maxOrderQty":1000000,
        //                 "maxPriceEp":100000000000000,
        //                 "lotSize":1,
        //                 "tickSize":"0.5",
        //                 "contractSize":"1 USD",
        //                 "priceScale":4,
        //                 "ratioScale":8,
        //                 "valueScale":8,
        //                 "defaultLeverage":0,
        //                 "maxLeverage":100,
        //                 "initMarginEr":"1000000",
        //                 "maintMarginEr":"500000",
        //                 "defaultRiskLimitEv":10000000000,
        //                 "deleverage":true,
        //                 "makerFeeRateEr":-250000,
        //                 "takerFeeRateEr":750000,
        //                 "fundingInterval":8,
        //                 "marketUrl":"https://phemex.com/trade/BTCUSD",
        //                 "description":"BTCUSD is a BTC/USD perpetual contract priced on the .BTC Index. Each contract is worth 1 USD of Bitcoin. Funding is paid and received every 8 hours. At UTC time => 00:00, 08:00, 16:00.",
        //                 "$type":"Perpetual"
        //             ),
        //         )
        //     }
        //
        $v2ProductsData = $this->safe_value($v2Products, 'data', array());
        $products = $this->safe_value($v2ProductsData, 'products', array());
        $riskLimits = $this->safe_value($v2ProductsData, 'riskLimits', array());
        $riskLimitsById = $this->index_by($riskLimits, 'symbol');
        $v1ProductsById = $this->index_by($v1ProductsData, 'symbol');
        $result = array();
        for ($i = 0; $i < count($products); $i++) {
            $market = $products[$i];
            $type = $this->safe_string_lower($market, 'type');
            if ($type === 'perpetual') {
                $id = $this->safe_string($market, 'symbol');
                $riskLimitValues = $this->safe_value($riskLimitsById, $id, array());
                $market = array_merge($market, $riskLimitValues);
                $v1ProductsValues = $this->safe_value($v1ProductsById, $id, array());
                $market = array_merge($market, $v1ProductsValues);
                $market = $this->parse_swap_market($market);
            } else {
                $market = $this->parse_spot_market($market);
            }
            $result[] = $market;
        }
        return $result;
    }

    public function fetch_currencies($params = array ()) {
        $response = yield $this->publicGetCfgV2Products ($params);
        //
        //     {
        //         "$code":0,
        //         "msg":"OK",
        //         "$data":{
        //             ...,
        //             "$currencies":array(
        //                 array("$currency":"BTC","$valueScale":8,"$minValueEv":1,"$maxValueEv":5000000000000000000,"$name":"Bitcoin"),
        //                 array("$currency":"USD","$valueScale":4,"$minValueEv":1,"$maxValueEv":500000000000000,"$name":"USD"),
        //                 array("$currency":"USDT","$valueScale":8,"$minValueEv":1,"$maxValueEv":5000000000000000000,"$name":"TetherUS"),
        //             ),
        //             ...
        //         }
        //     }
        $data = $this->safe_value($response, 'data', array());
        $currencies = $this->safe_value($data, 'currencies', array());
        $result = array();
        for ($i = 0; $i < count($currencies); $i++) {
            $currency = $currencies[$i];
            $id = $this->safe_string($currency, 'currency');
            $name = $this->safe_string($currency, 'name');
            $code = $this->safe_currency_code($id);
            $valueScale = $this->safe_integer($currency, 'valueScale');
            $minValueEv = $this->safe_float($currency, 'minValueEv');
            $maxValueEv = $this->safe_float($currency, 'maxValueEv');
            $minAmount = null;
            $maxAmount = null;
            $precision = null;
            if ($valueScale !== null) {
                $precision = pow(10, -$valueScale);
                $precision = floatval($this->decimal_to_precision($precision, ROUND, 0.00000001, $this->precisionMode));
                if ($minValueEv !== null) {
                    $minAmount = floatval($this->decimal_to_precision($minValueEv * $precision, ROUND, 0.00000001, $this->precisionMode));
                }
                if ($maxValueEv !== null) {
                    $maxAmount = floatval($this->decimal_to_precision($maxValueEv * $precision, ROUND, 0.00000001, $this->precisionMode));
                }
            }
            $result[$code] = array(
                'id' => $id,
                'info' => $currency,
                'code' => $code,
                'name' => $name,
                'active' => null,
                'fee' => null,
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => $minAmount,
                        'max' => $maxAmount,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'valueScale' => $valueScale,
            );
        }
        return $result;
    }

    public function parse_bid_ask($bidask, $priceKey = 0, $amountKey = 1, $market = null) {
        if ($market === null) {
            throw new ArgumentsRequired($this->id . ' parseBidAsk() requires a $market argument');
        }
        $amount = $this->safe_float($bidask, $amountKey);
        if ($market['spot']) {
            $amount = $this->from_ev($amount, $market);
        }
        return array(
            $this->from_ep($this->safe_float($bidask, $priceKey), $market),
            $amount,
        );
    }

    public function parse_order_book($orderbook, $timestamp = null, $bidsKey = 'bids', $asksKey = 'asks', $priceKey = 0, $amountKey = 1, $market = null) {
        $result = array(
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'nonce' => null,
        );
        $sides = array( $bidsKey, $asksKey );
        for ($i = 0; $i < count($sides); $i++) {
            $side = $sides[$i];
            $orders = array();
            $bidasks = $this->safe_value($orderbook, $side);
            for ($k = 0; $k < count($bidasks); $k++) {
                $orders[] = $this->parse_bid_ask($bidasks[$k], $priceKey, $amountKey, $market);
            }
            $result[$side] = $orders;
        }
        $result[$bidsKey] = $this->sort_by($result[$bidsKey], 0, true);
        $result[$asksKey] = $this->sort_by($result[$asksKey], 0);
        return $result;
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'id' => 123456789, // optional $request id
        );
        $response = yield $this->v1GetMdOrderbook (array_merge($request, $params));
        //
        //     {
        //         "error" => null,
        //         "id" => 0,
        //         "$result" => {
        //             "$book" => array(
        //                 "asks" => array(
        //                     array( 23415000000, 105262000 ),
        //                     array( 23416000000, 147914000 ),
        //                     array( 23419000000, 160914000 ),
        //                 ),
        //                 "bids" => array(
        //                     array( 23360000000, 32995000 ),
        //                     array( 23359000000, 221887000 ),
        //                     array( 23356000000, 284599000 ),
        //                 ),
        //             ),
        //             "depth" => 30,
        //             "sequence" => 1592059928,
        //             "$symbol" => "sETHUSDT",
        //             "$timestamp" => 1592387340020000955,
        //             "type" => "snapshot"
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $book = $this->safe_value($result, 'book', array());
        $timestamp = $this->safe_integer_product($result, 'timestamp', 0.000001);
        $orderbook = $this->parse_order_book($book, $timestamp, 'bids', 'asks', 0, 1, $market);
        $orderbook['nonce'] = $this->safe_integer($result, 'sequence');
        return $orderbook;
    }

    public function to_en($n, $scale, $precision) {
        return intval($this->decimal_to_precision($n * pow(10, $scale), ROUND, $precision, DECIMAL_PLACES));
    }

    public function to_ev($amount, $market = null) {
        if (($amount === null) || ($market === null)) {
            return $amount;
        }
        return $this->to_en($amount, $market['valueScale'], 0);
    }

    public function to_ep($price, $market = null) {
        if (($price === null) || ($market === null)) {
            return $price;
        }
        return $this->to_en($price, $market['priceScale'], 0);
    }

    public function from_en($en, $scale, $precision, $precisionMode = null) {
        if ($en === null) {
            return $en;
        }
        $precisionMode = ($precisionMode === null) ? $this->precisionMode : $precisionMode;
        return floatval($this->decimal_to_precision($en * pow(10, -$scale), ROUND, $precision, $precisionMode));
    }

    public function from_ep($ep, $market = null) {
        if (($ep === null) || ($market === null)) {
            return $ep;
        }
        return $this->from_en($ep, $market['priceScale'], $market['precision']['price']);
    }

    public function from_ev($ev, $market = null) {
        if (($ev === null) || ($market === null)) {
            return $ev;
        }
        if ($market['spot']) {
            return $this->from_en($ev, $market['valueScale'], $market['precision']['amount']);
        } else {
            return $this->from_en($ev, $market['valueScale'], 1 / pow(10, $market['valueScale']));
        }
    }

    public function from_er($er, $market = null) {
        if (($er === null) || ($market === null)) {
            return $er;
        }
        return $this->from_en($er, $market['ratioScale'], 0.00000001);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     array(
        //         1592467200, // timestamp
        //         300, // interval
        //         23376000000, // last
        //         23322000000, // open
        //         23381000000, // high
        //         23315000000, // low
        //         23367000000, // close
        //         208671000, // base volume
        //         48759063370, // quote volume
        //     )
        //
        $baseVolume = null;
        if (($market !== null) && $market['spot']) {
            $baseVolume = $this->from_ev($this->safe_float($ohlcv, 7), $market);
        } else {
            $baseVolume = $this->safe_integer($ohlcv, 7);
        }
        return array(
            $this->safe_timestamp($ohlcv, 0),
            $this->from_ep($this->safe_float($ohlcv, 3), $market),
            $this->from_ep($this->safe_float($ohlcv, 4), $market),
            $this->from_ep($this->safe_float($ohlcv, 5), $market),
            $this->from_ep($this->safe_float($ohlcv, 6), $market),
            $baseVolume,
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $request = array(
            // 'symbol' => $market['id'],
            'resolution' => $this->timeframes[$timeframe],
            // 'from' => 1588830682, // seconds
            // 'to' => $this->seconds(),
        );
        $duration = $this->parse_timeframe($timeframe);
        $now = $this->seconds();
        if ($since !== null) {
            if ($limit === null) {
                $limit = 2000; // max 2000
            }
            $since = intval($since / 1000);
            $request['from'] = $since;
            // time ranges ending in the future are not accepted
            // https://github.com/ccxt/ccxt/issues/8050
            $request['to'] = min ($now, $this->sum($since, $duration * $limit));
        } else if ($limit !== null) {
            $limit = min ($limit, 2000);
            $request['from'] = $now - $duration * $this->sum($limit, 1);
            $request['to'] = $now;
        } else {
            throw new ArgumentsRequired($this->id . ' fetchOHLCV() requires a $since argument, or a $limit argument, or both');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request['symbol'] = $market['id'];
        $response = yield $this->publicGetMdKline (array_merge($request, $params));
        //
        //     {
        //         "code":0,
        //         "msg":"OK",
        //         "$data":{
        //             "total":-1,
        //             "$rows":[
        //                 [1592467200,300,23376000000,23322000000,23381000000,23315000000,23367000000,208671000,48759063370],
        //                 [1592467500,300,23367000000,23314000000,23390000000,23311000000,23331000000,234820000,54848948710],
        //                 [1592467800,300,23331000000,23385000000,23391000000,23326000000,23387000000,152931000,35747882250],
        //             ]
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $rows = $this->safe_value($data, 'rows', array());
        return $this->parse_ohlcvs($rows, $market, $timeframe, $since, $limit);
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // spot
        //
        //     {
        //         "askEp" => 943836000000,
        //         "bidEp" => 943601000000,
        //         "highEp" => 955946000000,
        //         "lastEp" => 943803000000,
        //         "lowEp" => 924973000000,
        //         "openEp" => 948693000000,
        //         "$symbol" => "sBTCUSDT",
        //         "$timestamp" => 1592471203505728630,
        //         "turnoverEv" => 111822826123103,
        //         "volumeEv" => 11880532281
        //     }
        //
        // swap
        //
        //     {
        //         "askEp" => 2332500,
        //         "bidEp" => 2331000,
        //         "fundingRateEr" => 10000,
        //         "highEp" => 2380000,
        //         "indexEp" => 2329057,
        //         "lastEp" => 2331500,
        //         "lowEp" => 2274000,
        //         "markEp" => 2329232,
        //         "openEp" => 2337500,
        //         "openInterest" => 1298050,
        //         "predFundingRateEr" => 19921,
        //         "$symbol" => "ETHUSD",
        //         "$timestamp" => 1592474241582701416,
        //         "turnoverEv" => 47228362330,
        //         "volume" => 4053863
        //     }
        //
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->safe_integer_product($ticker, 'timestamp', 0.000001);
        $last = $this->from_ep($this->safe_float($ticker, 'lastEp'), $market);
        $quoteVolume = $this->from_ep($this->safe_float($ticker, 'turnoverEv'), $market);
        $baseVolume = $this->safe_float($ticker, 'volume');
        if ($baseVolume === null) {
            $baseVolume = $this->from_ev($this->safe_float($ticker, 'volumeEv'));
        }
        $vwap = null;
        if (($market !== null) && ($market['spot'])) {
            $vwap = $this->vwap($baseVolume, $quoteVolume);
        }
        $change = null;
        $percentage = null;
        $average = null;
        $open = $this->from_ep($this->safe_float($ticker, 'openEp'), $market);
        if (($open !== null) && ($last !== null)) {
            $change = $last - $open;
            if ($open > 0) {
                $percentage = $change / $open * 100;
            }
            $average = $this->sum($open, $last) / 2;
        }
        $result = array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->from_ep($this->safe_float($ticker, 'highEp'), $market),
            'low' => $this->from_ep($this->safe_float($ticker, 'lowEp'), $market),
            'bid' => $this->from_ep($this->safe_float($ticker, 'bidEp'), $market),
            'bidVolume' => null,
            'ask' => $this->from_ep($this->safe_float($ticker, 'askEp'), $market),
            'askVolume' => null,
            'vwap' => $vwap,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null, // previous day close
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
        return $result;
    }

    public function fetch_ticker($symbol, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'id' => 123456789, // optional $request id
        );
        $method = $market['spot'] ? 'v1GetMdSpotTicker24hr' : 'v1GetMdTicker24hr';
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "error" => null,
        //         "id" => 0,
        //         "$result" => {
        //             "askEp" => 943836000000,
        //             "bidEp" => 943601000000,
        //             "highEp" => 955946000000,
        //             "lastEp" => 943803000000,
        //             "lowEp" => 924973000000,
        //             "openEp" => 948693000000,
        //             "$symbol" => "sBTCUSDT",
        //             "timestamp" => 1592471203505728630,
        //             "turnoverEv" => 111822826123103,
        //             "volumeEv" => 11880532281
        //         }
        //     }
        //
        // swap
        //
        //     {
        //         "error" => null,
        //         "id" => 0,
        //         "$result" => {
        //             "askEp" => 2332500,
        //             "bidEp" => 2331000,
        //             "fundingRateEr" => 10000,
        //             "highEp" => 2380000,
        //             "indexEp" => 2329057,
        //             "lastEp" => 2331500,
        //             "lowEp" => 2274000,
        //             "markEp" => 2329232,
        //             "openEp" => 2337500,
        //             "openInterest" => 1298050,
        //             "predFundingRateEr" => 19921,
        //             "$symbol" => "ETHUSD",
        //             "timestamp" => 1592474241582701416,
        //             "turnoverEv" => 47228362330,
        //             "volume" => 4053863
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        return $this->parse_ticker($result, $market);
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'id' => 123456789, // optional $request id
        );
        $response = yield $this->v1GetMdTrade (array_merge($request, $params));
        //
        //     {
        //         "error" => null,
        //         "id" => 0,
        //         "$result" => {
        //             "sequence" => 1315644947,
        //             "$symbol" => "BTCUSD",
        //             "$trades" => array(
        //                 array( 1592541746712239749, 13156448570000, "Buy", 93070000, 40173 ),
        //                 array( 1592541740434625085, 13156447110000, "Sell", 93065000, 5000 ),
        //                 array( 1592541732958241616, 13156441390000, "Buy", 93070000, 3460 ),
        //             ),
        //             "type" => "snapshot"
        //         }
        //     }
        //
        $result = $this->safe_value($response, 'result', array());
        $trades = $this->safe_value($result, 'trades', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_trade($trade, $market = null) {
        //
        // fetchTrades (public)
        //
        //     array(
        //         1592541746712239749,
        //         13156448570000,
        //         "Buy",
        //         93070000,
        //         40173
        //     )
        //
        // fetchMyTrades (private)
        //
        // spot
        //
        //     {
        //         "qtyType" => "ByQuote",
        //         "transactTimeNs" => 1589450974800550100,
        //         "clOrdID" => "8ba59d40-df25-d4b0-14cf-0703f44e9690",
        //         "orderID" => "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83",
        //         "$symbol" => "sBTCUSDT",
        //         "$side" => "Buy",
        //         "priceEP" => 970056000000,
        //         "baseQtyEv" => 0,
        //         "quoteQtyEv" => 1000000000,
        //         "action" => "New",
        //         "$execStatus" => "MakerFill",
        //         "ordStatus" => "Filled",
        //         "ordType" => "Limit",
        //         "execInst" => "None",
        //         "timeInForce" => "GoodTillCancel",
        //         "stopDirection" => "UNSPECIFIED",
        //         "tradeType" => "Trade",
        //         "stopPxEp" => 0,
        //         "execId" => "c6bd8979-07ba-5946-b07e-f8b65135dbb1",
        //         "execPriceEp" => 970056000000,
        //         "execBaseQtyEv" => 103000,
        //         "execQuoteQtyEv" => 999157680,
        //         "leavesBaseQtyEv" => 0,
        //         "leavesQuoteQtyEv" => 0,
        //         "execFeeEv" => 0,
        //         "$feeRateEr" => 0
        //     }
        //
        // swap
        //
        //     {
        //         "transactTimeNs" => 1578026629824704800,
        //         "$symbol" => "BTCUSD",
        //         "currency" => "BTC",
        //         "action" => "Replace",
        //         "$side" => "Sell",
        //         "tradeType" => "Trade",
        //         "execQty" => 700,
        //         "execPriceEp" => 71500000,
        //         "orderQty" => 700,
        //         "priceEp" => 71500000,
        //         "execValueEv" => 9790209,
        //         "$feeRateEr" => -25000,
        //         "execFeeEv" => -2447,
        //         "ordType" => "Limit",
        //         "execID" => "b01671a1-5ddc-5def-b80a-5311522fd4bf",
        //         "orderID" => "b63bc982-be3a-45e0-8974-43d6375fb626",
        //         "clOrdID" => "uuid-1577463487504",
        //         "$execStatus" => "MakerFill"
        //     }
        //
        $price = null;
        $amount = null;
        $timestamp = null;
        $id = null;
        $side = null;
        $cost = null;
        $type = null;
        $fee = null;
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $orderId = null;
        $takerOrMaker = null;
        if (gettype($trade) === 'array' && count(array_filter(array_keys($trade), 'is_string')) == 0) {
            $tradeLength = is_array($trade) ? count($trade) : 0;
            $timestamp = $this->safe_integer_product($trade, 0, 0.000001);
            if ($tradeLength > 4) {
                $id = $this->safe_string($trade, $tradeLength - 4);
            }
            $side = $this->safe_string_lower($trade, $tradeLength - 3);
            $price = $this->from_ep($this->safe_float($trade, $tradeLength - 2), $market);
            $amount = $this->from_ev($this->safe_float($trade, $tradeLength - 1), $market);
            if ($market['spot']) {
                if (($price !== null) && ($amount !== null)) {
                    $cost = $price * $amount;
                }
            }
        } else {
            $timestamp = $this->safe_integer_product($trade, 'transactTimeNs', 0.000001);
            $id = $this->safe_string_2($trade, 'execId', 'execID');
            $orderId = $this->safe_string($trade, 'orderID');
            $side = $this->safe_string_lower($trade, 'side');
            $type = $this->parse_order_type($this->safe_string($trade, 'ordType'));
            $execStatus = $this->safe_string($trade, 'execStatus');
            if ($execStatus === 'MakerFill') {
                $takerOrMaker = 'maker';
            }
            $price = $this->from_ep($this->safe_float($trade, 'execPriceEp'), $market);
            $amount = $this->from_ev($this->safe_float($trade, 'execBaseQtyEv'), $market);
            $amount = $this->safe_float($trade, 'execQty', $amount);
            $cost = $this->from_ev($this->safe_float_2($trade, 'execQuoteQtyEv', 'execValueEv'), $market);
            $feeCost = $this->from_ev($this->safe_float($trade, 'execFeeEv'), $market);
            if ($feeCost !== null) {
                $feeRate = null;
                $feeRateEr = $this->safe_float($trade, 'feeRateEr');
                if ($feeRateEr < 0) {
                    $feeRateEr = abs($feeRateEr);
                    $feeRate = $this->from_er($feeRateEr, $market);
                    $feeRate = -$feeRate;
                } else {
                    $feeRate = $this->from_er($feeRateEr, $market);
                }
                $feeCurrencyCode = null;
                if ($market['spot']) {
                    $feeCurrencyCode = ($side === 'buy') ? $market['base'] : $market['quote'];
                } else {
                    $info = $this->safe_value($market, 'info');
                    if ($info !== null) {
                        $settlementCurrencyId = $this->safe_string($info, 'settlementCurrency');
                        $feeCurrencyCode = $this->safe_currency_code($settlementCurrencyId);
                    }
                }
                $fee = array(
                    'cost' => $feeCost,
                    'rate' => $feeRate,
                    'currency' => $feeCurrencyCode,
                );
            }
        }
        return array(
            'info' => $trade,
            'id' => $id,
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'order' => $orderId,
            'type' => $type,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function parse_spot_balance($response) {
        //
        //     {
        //         "$code":0,
        //         "msg":"",
        //         "$data":array(
        //             array(
        //                 "$currency":"USDT",
        //                 "$balanceEv":0,
        //                 "$lockedTradingBalanceEv":0,
        //                 "$lockedWithdrawEv":0,
        //                 "lastUpdateTimeNs":1592065834511322514,
        //                 "walletVid":0
        //             ),
        //             {
        //                 "$currency":"ETH",
        //                 "$balanceEv":0,
        //                 "$lockedTradingBalanceEv":0,
        //                 "$lockedWithdrawEv":0,
        //                 "lastUpdateTimeNs":1592065834511322514,
        //                 "walletVid":0
        //             }
        //         )
        //     }
        //
        $result = array( 'info' => $response );
        $data = $this->safe_value($response, 'data', array());
        for ($i = 0; $i < count($data); $i++) {
            $balance = $data[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $currency = $this->safe_value($this->currencies, $code, array());
            $scale = $this->safe_integer($currency, 'valueScale', 8);
            $account = $this->account();
            $balanceEv = $this->safe_float($balance, 'balanceEv');
            $lockedTradingBalanceEv = $this->safe_float($balance, 'lockedTradingBalanceEv');
            $lockedWithdrawEv = $this->safe_float($balance, 'lockedWithdrawEv');
            $total = $this->from_en($balanceEv, $scale, $scale, DECIMAL_PLACES);
            $lockedTradingBalance = $this->from_en($lockedTradingBalanceEv, $scale, $scale, DECIMAL_PLACES);
            $lockedWithdraw = $this->from_en($lockedWithdrawEv, $scale, $scale, DECIMAL_PLACES);
            $used = $this->sum($lockedTradingBalance, $lockedWithdraw);
            $account['total'] = $total;
            $account['used'] = $used;
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function parse_swap_balance($response) {
        //
        //     {
        //         "$code":0,
        //         "msg":"",
        //         "$data":{
        //             "$account":array(
        //                 "accountId":6192120001,
        //                 "$currency":"BTC",
        //                 "$accountBalanceEv":1254744,
        //                 "$totalUsedBalanceEv":0,
        //                 "bonusBalanceEv":1254744
        //             ),
        //             "positions":array(
        //                 {
        //                     "accountID":6192120001,
        //                     "symbol":"BTCUSD",
        //                     "$currency":"BTC",
        //                     "side":"None",
        //                     "positionStatus":"Normal",
        //                     "crossMargin":false,
        //                     "leverageEr":0,
        //                     "leverage":0E-8,
        //                     "initMarginReqEr":1000000,
        //                     "initMarginReq":0.01000000,
        //                     "maintMarginReqEr":500000,
        //                     "maintMarginReq":0.00500000,
        //                     "riskLimitEv":10000000000,
        //                     "riskLimit":100.00000000,
        //                     "size":0,
        //                     "value":0E-8,
        //                     "valueEv":0,
        //                     "avgEntryPriceEp":0,
        //                     "avgEntryPrice":0E-8,
        //                     "posCostEv":0,
        //                     "posCost":0E-8,
        //                     "assignedPosBalanceEv":0,
        //                     "assignedPosBalance":0E-8,
        //                     "bankruptCommEv":0,
        //                     "bankruptComm":0E-8,
        //                     "bankruptPriceEp":0,
        //                     "bankruptPrice":0E-8,
        //                     "positionMarginEv":0,
        //                     "positionMargin":0E-8,
        //                     "liquidationPriceEp":0,
        //                     "liquidationPrice":0E-8,
        //                     "deleveragePercentileEr":0,
        //                     "deleveragePercentile":0E-8,
        //                     "buyValueToCostEr":1150750,
        //                     "buyValueToCost":0.01150750,
        //                     "sellValueToCostEr":1149250,
        //                     "sellValueToCost":0.01149250,
        //                     "markPriceEp":96359083,
        //                     "markPrice":9635.90830000,
        //                     "markValueEv":0,
        //                     "markValue":null,
        //                     "unRealisedPosLossEv":0,
        //                     "unRealisedPosLoss":null,
        //                     "estimatedOrdLossEv":0,
        //                     "estimatedOrdLoss":0E-8,
        //                     "usedBalanceEv":0,
        //                     "usedBalance":0E-8,
        //                     "takeProfitEp":0,
        //                     "takeProfit":null,
        //                     "stopLossEp":0,
        //                     "stopLoss":null,
        //                     "realisedPnlEv":0,
        //                     "realisedPnl":null,
        //                     "cumRealisedPnlEv":0,
        //                     "cumRealisedPnl":null
        //                 }
        //             )
        //         }
        //     }
        //
        $result = array( 'info' => $response );
        $data = $this->safe_value($response, 'data', array());
        $balance = $this->safe_value($data, 'account', array());
        $currencyId = $this->safe_string($balance, 'currency');
        $code = $this->safe_currency_code($currencyId);
        $currency = $this->currency($code);
        $account = $this->account();
        $accountBalanceEv = $this->safe_float($balance, 'accountBalanceEv');
        $totalUsedBalanceEv = $this->safe_float($balance, 'totalUsedBalanceEv');
        $valueScale = $this->safe_integer($currency, 'valueScale', 8);
        $account['total'] = $this->from_en($accountBalanceEv, $valueScale, $valueScale, DECIMAL_PLACES);
        $account['used'] = $this->from_en($totalUsedBalanceEv, $valueScale, $valueScale, DECIMAL_PLACES);
        $result[$code] = $account;
        return $this->parse_balance($result);
    }

    public function fetch_balance($params = array ()) {
        yield $this->load_markets();
        $defaultType = $this->safe_string_2($this->options, 'defaultType', 'fetchBalance', 'spot');
        $type = $this->safe_string($params, 'type', $defaultType);
        $method = 'privateGetSpotWallets';
        $request = array();
        if ($type === 'swap') {
            $code = $this->safe_string($params, 'code');
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['currency'] = $currency['id'];
                $params = $this->omit($params, 'code');
            } else {
                $currency = $this->safe_string($params, 'currency');
                if ($currency === null) {
                    throw new ArgumentsRequired($this->id . ' fetchBalance() requires a $code parameter or a $currency parameter for ' . $type . ' type');
                }
            }
            $method = 'privateGetAccountsAccountPositions';
        }
        $params = $this->omit($params, 'type');
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "$code":0,
        //         "msg":"",
        //         "data":array(
        //             array(
        //                 "$currency":"USDT",
        //                 "balanceEv":0,
        //                 "lockedTradingBalanceEv":0,
        //                 "lockedWithdrawEv":0,
        //                 "lastUpdateTimeNs":1592065834511322514,
        //                 "walletVid":0
        //             ),
        //             {
        //                 "$currency":"ETH",
        //                 "balanceEv":0,
        //                 "lockedTradingBalanceEv":0,
        //                 "lockedWithdrawEv":0,
        //                 "lastUpdateTimeNs":1592065834511322514,
        //                 "walletVid":0
        //             }
        //         )
        //     }
        //
        // swap
        //
        //     {
        //         "$code":0,
        //         "msg":"",
        //         "data":{
        //             "account":array(
        //                 "accountId":6192120001,
        //                 "$currency":"BTC",
        //                 "accountBalanceEv":1254744,
        //                 "totalUsedBalanceEv":0,
        //                 "bonusBalanceEv":1254744
        //             ),
        //             "positions":array(
        //                 {
        //                     "accountID":6192120001,
        //                     "symbol":"BTCUSD",
        //                     "$currency":"BTC",
        //                     "side":"None",
        //                     "positionStatus":"Normal",
        //                     "crossMargin":false,
        //                     "leverageEr":0,
        //                     "leverage":0E-8,
        //                     "initMarginReqEr":1000000,
        //                     "initMarginReq":0.01000000,
        //                     "maintMarginReqEr":500000,
        //                     "maintMarginReq":0.00500000,
        //                     "riskLimitEv":10000000000,
        //                     "riskLimit":100.00000000,
        //                     "size":0,
        //                     "value":0E-8,
        //                     "valueEv":0,
        //                     "avgEntryPriceEp":0,
        //                     "avgEntryPrice":0E-8,
        //                     "posCostEv":0,
        //                     "posCost":0E-8,
        //                     "assignedPosBalanceEv":0,
        //                     "assignedPosBalance":0E-8,
        //                     "bankruptCommEv":0,
        //                     "bankruptComm":0E-8,
        //                     "bankruptPriceEp":0,
        //                     "bankruptPrice":0E-8,
        //                     "positionMarginEv":0,
        //                     "positionMargin":0E-8,
        //                     "liquidationPriceEp":0,
        //                     "liquidationPrice":0E-8,
        //                     "deleveragePercentileEr":0,
        //                     "deleveragePercentile":0E-8,
        //                     "buyValueToCostEr":1150750,
        //                     "buyValueToCost":0.01150750,
        //                     "sellValueToCostEr":1149250,
        //                     "sellValueToCost":0.01149250,
        //                     "markPriceEp":96359083,
        //                     "markPrice":9635.90830000,
        //                     "markValueEv":0,
        //                     "markValue":null,
        //                     "unRealisedPosLossEv":0,
        //                     "unRealisedPosLoss":null,
        //                     "estimatedOrdLossEv":0,
        //                     "estimatedOrdLoss":0E-8,
        //                     "usedBalanceEv":0,
        //                     "usedBalance":0E-8,
        //                     "takeProfitEp":0,
        //                     "takeProfit":null,
        //                     "stopLossEp":0,
        //                     "stopLoss":null,
        //                     "realisedPnlEv":0,
        //                     "realisedPnl":null,
        //                     "cumRealisedPnlEv":0,
        //                     "cumRealisedPnl":null
        //                 }
        //             )
        //         }
        //     }
        //
        $result = ($type === 'swap') ? $this->parse_swap_balance($response) : $this->parse_spot_balance($response);
        return $result;
    }

    public function parse_order_status($status) {
        $statuses = array(
            'Created' => 'open',
            'Untriggered' => 'open',
            'Deactivated' => 'closed',
            'Triggered' => 'open',
            'Rejected' => 'rejected',
            'New' => 'open',
            'PartiallyFilled' => 'open',
            'Filled' => 'closed',
            'Canceled' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type($type) {
        $types = array(
            'Limit' => 'limit',
            'Market' => 'market',
        );
        return $this->safe_string($types, $type, $type);
    }

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

    public function parse_spot_order($order, $market = null) {
        //
        // spot
        //
        //     {
        //         "orderID" => "d1d09454-cabc-4a23-89a7-59d43363f16d",
        //         "clOrdID" => "309bcd5c-9f6e-4a68-b775-4494542eb5cb",
        //         "priceEp" => 0,
        //         "action" => "New",
        //         "trigger" => "UNSPECIFIED",
        //         "pegPriceType" => "UNSPECIFIED",
        //         "stopDirection" => "UNSPECIFIED",
        //         "bizError" => 0,
        //         "$symbol" => "sBTCUSDT",
        //         "$side" => "Buy",
        //         "baseQtyEv" => 0,
        //         "ordType" => "Limit",
        //         "$timeInForce" => "GoodTillCancel",
        //         "ordStatus" => "Created",
        //         "cumFeeEv" => 0,
        //         "cumBaseQtyEv" => 0,
        //         "cumQuoteQtyEv" => 0,
        //         "leavesBaseQtyEv" => 0,
        //         "leavesQuoteQtyEv" => 0,
        //         "avgPriceEp" => 0,
        //         "cumBaseAmountEv" => 0,
        //         "cumQuoteAmountEv" => 0,
        //         "quoteQtyEv" => 0,
        //         "qtyType" => "ByBase",
        //         "stopPxEp" => 0,
        //         "pegOffsetValueEp" => 0
        //     }
        //
        //     {
        //         "orderID":"99232c3e-3d6a-455f-98cc-2061cdfe91bc",
        //         "stopPxEp":0,
        //         "avgPriceEp":0,
        //         "qtyType":"ByBase",
        //         "leavesBaseQtyEv":0,
        //         "leavesQuoteQtyEv":0,
        //         "baseQtyEv":"1000000000",
        //         "feeCurrency":"4",
        //         "stopDirection":"UNSPECIFIED",
        //         "$symbol":"sETHUSDT",
        //         "$side":"Buy",
        //         "quoteQtyEv":250000000000,
        //         "priceEp":25000000000,
        //         "ordType":"Limit",
        //         "$timeInForce":"GoodTillCancel",
        //         "ordStatus":"Rejected",
        //         "execStatus":"NewRejected",
        //         "createTimeNs":1592675305266037130,
        //         "cumFeeEv":0,
        //         "cumBaseValueEv":0,
        //         "cumQuoteValueEv":0
        //     }
        //
        $id = $this->safe_string($order, 'orderID');
        $clientOrderId = $this->safe_string($order, 'clOrdID');
        if (($clientOrderId !== null) && (strlen($clientOrderId) < 1)) {
            $clientOrderId = null;
        }
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $price = $this->from_ep($this->safe_float($order, 'priceEp'), $market);
        if ($price === 0) {
            $price = null;
        }
        $amount = $this->from_ev($this->safe_float($order, 'baseQtyEv'), $market);
        $remaining = $this->from_ev($this->safe_float($order, 'leavesBaseQtyEv'), $market);
        $filled = $this->from_ev($this->safe_float($order, 'cumBaseQtyEv'), $market);
        $cost = $this->from_ev($this->safe_float($order, 'quoteQtyEv'), $market);
        $average = $this->from_ep($this->safe_float($order, 'avgPriceEp'), $market);
        $status = $this->parse_order_status($this->safe_string($order, 'ordStatus'));
        $side = $this->safe_string_lower($order, 'side');
        $type = $this->parse_order_type($this->safe_string($order, 'ordType'));
        $timestamp = $this->safe_integer_product_2($order, 'actionTimeNs', 'createTimeNs', 0.000001);
        $fee = null;
        $feeCost = $this->from_ev($this->safe_float($order, 'cumFeeEv'), $market);
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => null,
            );
        }
        if ($filled === null) {
            if (($amount !== null) && ($remaining !== null)) {
                $filled = min (0, $amount - $remaining);
            }
        }
        $timeInForce = $this->parse_time_in_force($this->safe_string($order, 'timeInForce'));
        $stopPrice = $this->from_ep($this->safe_float($order, 'stopPxEp', $market));
        $postOnly = ($timeInForce === 'PO');
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        );
    }

    public function parse_swap_order($order, $market = null) {
        //
        //     {
        //         "bizError":0,
        //         "orderID":"7a1ad384-44a3-4e54-a102-de4195a29e32",
        //         "clOrdID":"",
        //         "$symbol":"ETHUSD",
        //         "$side":"Buy",
        //         "actionTimeNs":1592668973945065381,
        //         "transactTimeNs":0,
        //         "orderType":"Market",
        //         "priceEp":2267500,
        //         "$price":226.75000000,
        //         "orderQty":1,
        //         "displayQty":0,
        //         "$timeInForce":"ImmediateOrCancel",
        //         "reduceOnly":false,
        //         "closedPnlEv":0,
        //         "closedPnl":0E-8,
        //         "closedSize":0,
        //         "cumQty":0,
        //         "cumValueEv":0,
        //         "cumValue":0E-8,
        //         "leavesQty":1,
        //         "leavesValueEv":11337,
        //         "leavesValue":1.13370000,
        //         "stopDirection":"UNSPECIFIED",
        //         "stopPxEp":0,
        //         "stopPx":0E-8,
        //         "trigger":"UNSPECIFIED",
        //         "pegOffsetValueEp":0,
        //         "execStatus":"PendingNew",
        //         "pegPriceType":"UNSPECIFIED",
        //         "ordStatus":"Created"
        //     }
        //
        $id = $this->safe_string($order, 'orderID');
        $clientOrderId = $this->safe_string($order, 'clOrdID');
        if (($clientOrderId !== null) && (strlen($clientOrderId) < 1)) {
            $clientOrderId = null;
        }
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $status = $this->parse_order_status($this->safe_string($order, 'ordStatus'));
        $side = $this->safe_string_lower($order, 'side');
        $type = $this->parse_order_type($this->safe_string($order, 'orderType'));
        $price = $this->from_ep($this->safe_float($order, 'priceEp'), $market);
        $amount = $this->safe_float($order, 'orderQty');
        $filled = $this->safe_float($order, 'cumQty');
        $remaining = $this->safe_float($order, 'leavesQty');
        $timestamp = $this->safe_integer_product($order, 'actionTimeNs', 0.000001);
        $cost = $this->safe_float($order, 'cumValue');
        $lastTradeTimestamp = $this->safe_integer_product($order, 'transactTimeNs', 0.000001);
        if ($lastTradeTimestamp === 0) {
            $lastTradeTimestamp = null;
        }
        $timeInForce = $this->parse_time_in_force($this->safe_string($order, 'timeInForce'));
        $stopPrice = $this->safe_float($order, 'stopPx');
        $postOnly = ($timeInForce === 'PO');
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'datetime' => $this->iso8601($timestamp),
            'timestamp' => $timestamp,
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'cost' => $cost,
            'average' => null,
            'status' => $status,
            'fee' => null,
            'trades' => null,
        );
    }

    public function parse_order($order, $market = null) {
        if (is_array($order) && array_key_exists('closedPnl', $order)) {
            return $this->parse_swap_order($order, $market);
        }
        return $this->parse_spot_order($order, $market);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $side = $this->capitalize($side);
        $type = $this->capitalize($type);
        $request = array(
            // common
            'symbol' => $market['id'],
            'side' => $side, // Sell, Buy
            'ordType' => $type, // Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched or Pegged for swap orders
            // 'stopPxEp' => $this->to_ep(stopPx, $market), // for conditional orders
            // 'priceEp' => $this->to_ep($price, $market), // required for limit orders
            // 'timeInForce' => 'GoodTillCancel', // GoodTillCancel, PostOnly, ImmediateOrCancel, FillOrKill
            // ----------------------------------------------------------------
            // spot
            // 'qtyType' => 'ByBase', // ByBase, ByQuote
            // 'quoteQtyEv' => $this->to_ep($cost, $market),
            // 'baseQtyEv' => $this->to_ev($amount, $market),
            // 'trigger' => 'ByLastPrice', // required for conditional orders
            // ----------------------------------------------------------------
            // swap
            // 'clOrdID' => $this->uuid(), // max length 40
            // 'orderQty' => $this->amount_to_precision($amount, $symbol),
            // 'reduceOnly' => false,
            // 'closeOnTrigger' => false, // implicit reduceOnly and cancel other orders in the same direction
            // 'takeProfitEp' => $this->to_ep(takeProfit, $market),
            // 'stopLossEp' => $this->to_ep(stopLossEp, $market),
            // 'triggerType' => 'ByMarkPrice', // ByMarkPrice, ByLastPrice
            // 'pegOffsetValueEp' => integer, // Trailing offset from current $price-> Negative value when position is long, positive when position is short
            // 'pegPriceType' => 'TrailingStopPeg', // TrailingTakeProfitPeg
            // 'text' => 'comment',
        );
        if ($market['spot']) {
            $qtyType = $this->safe_value($params, 'qtyType', 'ByBase');
            if (($type === 'Market') || ($type === 'Stop') || ($type === 'MarketIfTouched')) {
                if ($price !== null) {
                    $qtyType = 'ByQuote';
                }
            }
            $request['qtyType'] = $qtyType;
            if ($qtyType === 'ByQuote') {
                $cost = $this->safe_float($params, 'cost');
                $params = $this->omit($params, 'cost');
                if ($this->options['createOrderByQuoteRequiresPrice']) {
                    if ($price !== null) {
                        $cost = $amount * $price;
                    } else if ($cost === null) {
                        throw new ArgumentsRequired($this->id . ' createOrder() ' . $qtyType . ' requires a $price argument or a $cost parameter');
                    }
                }
                $cost = ($cost === null) ? $amount : $cost;
                $request['quoteQtyEv'] = $this->to_ep($cost, $market);
            } else {
                $request['baseQtyEv'] = $this->to_ev($amount, $market);
            }
        } else if ($market['swap']) {
            $request['orderQty'] = intval($amount);
        }
        if ($type === 'Limit') {
            $request['priceEp'] = $this->to_ep($price, $market);
        }
        $stopPrice = $this->safe_float_2($params, 'stopPx', 'stopPrice');
        if ($stopPrice !== null) {
            $request['stopPxEp'] = $this->to_ep($stopPrice, $market);
        }
        $params = $this->omit($params, array( 'stopPx', 'stopPrice' ));
        $method = $market['spot'] ? 'privatePostSpotOrders' : 'privatePostOrders';
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code" => 0,
        //         "msg" => "",
        //         "$data" => {
        //             "orderID" => "d1d09454-cabc-4a23-89a7-59d43363f16d",
        //             "clOrdID" => "309bcd5c-9f6e-4a68-b775-4494542eb5cb",
        //             "priceEp" => 0,
        //             "action" => "New",
        //             "trigger" => "UNSPECIFIED",
        //             "pegPriceType" => "UNSPECIFIED",
        //             "stopDirection" => "UNSPECIFIED",
        //             "bizError" => 0,
        //             "$symbol" => "sBTCUSDT",
        //             "$side" => "Buy",
        //             "baseQtyEv" => 0,
        //             "ordType" => "Limit",
        //             "timeInForce" => "GoodTillCancel",
        //             "ordStatus" => "Created",
        //             "cumFeeEv" => 0,
        //             "cumBaseQtyEv" => 0,
        //             "cumQuoteQtyEv" => 0,
        //             "leavesBaseQtyEv" => 0,
        //             "leavesQuoteQtyEv" => 0,
        //             "avgPriceEp" => 0,
        //             "cumBaseAmountEv" => 0,
        //             "cumQuoteAmountEv" => 0,
        //             "quoteQtyEv" => 0,
        //             "$qtyType" => "ByBase",
        //             "stopPxEp" => 0,
        //             "pegOffsetValueEp" => 0
        //         }
        //     }
        //
        // swap
        //
        //     {
        //         "code":0,
        //         "msg":"",
        //         "$data":{
        //             "bizError":0,
        //             "orderID":"7a1ad384-44a3-4e54-a102-de4195a29e32",
        //             "clOrdID":"",
        //             "$symbol":"ETHUSD",
        //             "$side":"Buy",
        //             "actionTimeNs":1592668973945065381,
        //             "transactTimeNs":0,
        //             "orderType":"Market",
        //             "priceEp":2267500,
        //             "$price":226.75000000,
        //             "orderQty":1,
        //             "displayQty":0,
        //             "timeInForce":"ImmediateOrCancel",
        //             "reduceOnly":false,
        //             "closedPnlEv":0,
        //             "closedPnl":0E-8,
        //             "closedSize":0,
        //             "cumQty":0,
        //             "cumValueEv":0,
        //             "cumValue":0E-8,
        //             "leavesQty":1,
        //             "leavesValueEv":11337,
        //             "leavesValue":1.13370000,
        //             "stopDirection":"UNSPECIFIED",
        //             "stopPxEp":0,
        //             "stopPx":0E-8,
        //             "trigger":"UNSPECIFIED",
        //             "pegOffsetValueEp":0,
        //             "execStatus":"PendingNew",
        //             "pegPriceType":"UNSPECIFIED",
        //             "ordStatus":"Created"
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'clOrdID');
        $params = $this->omit($params, array( 'clientOrderId', 'clOrdID' ));
        if ($clientOrderId !== null) {
            $request['clOrdID'] = $clientOrderId;
        } else {
            $request['orderID'] = $id;
        }
        $method = $market['spot'] ? 'privateDeleteSpotOrders' : 'privateDeleteOrdersCancel';
        $response = yield $this->$method (array_merge($request, $params));
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            // 'symbol' => $market['id'],
            // 'untriggerred' => false, // false to cancel non-conditional orders, true to cancel conditional orders
            // 'text' => 'up to 40 characters max',
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new NotSupported($this->id . ' cancelAllOrders() supports swap $market type orders only');
            }
            $request['symbol'] = $market['id'];
        }
        return yield $this->privateDeleteOrdersAll (array_merge($request, $params));
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $method = $market['spot'] ? 'privateGetSpotOrdersActive' : 'privateGetExchangeOrder';
        $request = array(
            'symbol' => $market['id'],
        );
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'clOrdID');
        $params = $this->omit($params, array( 'clientOrderId', 'clOrdID' ));
        if ($clientOrderId !== null) {
            $request['clOrdID'] = $clientOrderId;
        } else {
            $request['orderID'] = $id;
        }
        $response = yield $this->$method (array_merge($request, $params));
        $data = $this->safe_value($response, 'data', array());
        $order = $data;
        if (gettype($data) === 'array' && count(array_filter(array_keys($data), 'is_string')) == 0) {
            $numOrders = is_array($data) ? count($data) : 0;
            if ($numOrders < 1) {
                if ($clientOrderId !== null) {
                    throw new OrderNotFound($this->id . ' fetchOrder ' . $symbol . ' $order with $clientOrderId ' . $clientOrderId . ' not found');
                } else {
                    throw new OrderNotFound($this->id . ' fetchOrder ' . $symbol . ' $order with $id ' . $id . ' not found');
                }
            }
            $order = $this->safe_value($data, 0, array());
        }
        return $this->parse_order($order, $market);
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $method = $market['spot'] ? 'privateGetSpotOrders' : 'privateGetExchangeOrderList';
        $request = array(
            'symbol' => $market['id'],
        );
        if ($since !== null) {
            $request['start'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = yield $this->$method (array_merge($request, $params));
        $data = $this->safe_value($response, 'data', array());
        $rows = $this->safe_value($data, 'rows', array());
        return $this->parse_orders($rows, $market, $since, $limit);
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOpenOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $method = $market['spot'] ? 'privateGetSpotOrders' : 'privateGetOrdersActiveList';
        $request = array(
            'symbol' => $market['id'],
        );
        try {
            $response = yield $this->$method (array_merge($request, $params));
            $data = $this->safe_value($response, 'data', array());
            if (gettype($data) === 'array' && count(array_filter(array_keys($data), 'is_string')) == 0) {
                return $this->parse_orders($data, $market, $since, $limit);
            } else {
                $rows = $this->safe_value($data, 'rows', array());
                return $this->parse_orders($rows, $market, $since, $limit);
            }
        } catch (Exception $e) {
            return array();
        }
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchClosedOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $method = $market['spot'] ? 'privateGetExchangeSpotOrder' : 'privateGetExchangeOrderList';
        $request = array(
            'symbol' => $market['id'],
        );
        if ($since !== null) {
            $request['start'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code":0,
        //         "msg":"OK",
        //         "$data":{
        //             "total":8,
        //             "$rows":array(
        //                 array(
        //                     "orderID":"99232c3e-3d6a-455f-98cc-2061cdfe91bc",
        //                     "stopPxEp":0,
        //                     "avgPriceEp":0,
        //                     "qtyType":"ByBase",
        //                     "leavesBaseQtyEv":0,
        //                     "leavesQuoteQtyEv":0,
        //                     "baseQtyEv":"1000000000",
        //                     "feeCurrency":"4",
        //                     "stopDirection":"UNSPECIFIED",
        //                     "$symbol":"sETHUSDT",
        //                     "side":"Buy",
        //                     "quoteQtyEv":250000000000,
        //                     "priceEp":25000000000,
        //                     "ordType":"Limit",
        //                     "timeInForce":"GoodTillCancel",
        //                     "ordStatus":"Rejected",
        //                     "execStatus":"NewRejected",
        //                     "createTimeNs":1592675305266037130,
        //                     "cumFeeEv":0,
        //                     "cumBaseValueEv":0,
        //                     "cumQuoteValueEv":0
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        if (gettype($data) === 'array' && count(array_filter(array_keys($data), 'is_string')) == 0) {
            return $this->parse_orders($data, $market, $since, $limit);
        } else {
            $rows = $this->safe_value($data, 'rows', array());
            return $this->parse_orders($rows, $market, $since, $limit);
        }
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchClosedOrders() requires a $symbol argument');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $method = $market['spot'] ? 'privateGetExchangeSpotOrderTrades' : 'privateGetExchangeOrderTrade';
        $request = array(
            'symbol' => $market['id'],
        );
        if ($since !== null) {
            $request['start'] = $since;
        }
        if ($market['swap'] && ($limit !== null)) {
            $request['limit'] = $limit;
        }
        $response = yield $this->$method (array_merge($request, $params));
        //
        // spot
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "$data" => {
        //             "total" => 1,
        //             "$rows" => array(
        //                 {
        //                     "qtyType" => "ByQuote",
        //                     "transactTimeNs" => 1589450974800550100,
        //                     "clOrdID" => "8ba59d40-df25-d4b0-14cf-0703f44e9690",
        //                     "orderID" => "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83",
        //                     "$symbol" => "sBTCUSDT",
        //                     "side" => "Buy",
        //                     "priceEP" => 970056000000,
        //                     "baseQtyEv" => 0,
        //                     "quoteQtyEv" => 1000000000,
        //                     "action" => "New",
        //                     "execStatus" => "MakerFill",
        //                     "ordStatus" => "Filled",
        //                     "ordType" => "Limit",
        //                     "execInst" => "None",
        //                     "timeInForce" => "GoodTillCancel",
        //                     "stopDirection" => "UNSPECIFIED",
        //                     "tradeType" => "Trade",
        //                     "stopPxEp" => 0,
        //                     "execId" => "c6bd8979-07ba-5946-b07e-f8b65135dbb1",
        //                     "execPriceEp" => 970056000000,
        //                     "execBaseQtyEv" => 103000,
        //                     "execQuoteQtyEv" => 999157680,
        //                     "leavesBaseQtyEv" => 0,
        //                     "leavesQuoteQtyEv" => 0,
        //                     "execFeeEv" => 0,
        //                     "feeRateEr" => 0
        //                 }
        //             )
        //         }
        //     }
        //
        //
        // swap
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "$data" => {
        //             "total" => 79,
        //             "$rows" => array(
        //                 array(
        //                     "transactTimeNs" => 1606054879331565300,
        //                     "$symbol" => "BTCUSD",
        //                     "currency" => "BTC",
        //                     "action" => "New",
        //                     "side" => "Buy",
        //                     "tradeType" => "Trade",
        //                     "execQty" => 5,
        //                     "execPriceEp" => 182990000,
        //                     "orderQty" => 5,
        //                     "priceEp" => 183870000,
        //                     "execValueEv" => 27323,
        //                     "feeRateEr" => 75000,
        //                     "execFeeEv" => 21,
        //                     "ordType" => "Market",
        //                     "execID" => "5eee56a4-04a9-5677-8eb0-c2fe22ae3645",
        //                     "orderID" => "ee0acb82-f712-4543-a11d-d23efca73197",
        //                     "clOrdID" => "",
        //                     "execStatus" => "TakerFill"
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $rows = $this->safe_value($data, 'rows', array());
        return $this->parse_trades($rows, $market, $since, $limit);
    }

    public function fetch_deposit_address($code, $params = array ()) {
        yield $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
        );
        $response = yield $this->privateGetPhemexUserWalletsV2DepositAddress (array_merge($request, $params));
        //     {
        //         "$code":0,
        //         "msg":"OK",
        //         "$data":{
        //             "$address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad",
        //             "$tag":null
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $address = $this->safe_string($data, 'address');
        $tag = $this->safe_string($data, 'tag');
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => $tag,
            'info' => $response,
        );
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        $response = yield $this->privateGetExchangeWalletsDepositList ($params);
        //
        //     {
        //         "$code":0,
        //         "msg":"OK",
        //         "$data":array(
        //             {
        //                 "id":29200,
        //                 "$currency":"USDT",
        //                 "currencyCode":3,
        //                 "txHash":"0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d",
        //                 "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad",
        //                 "amountEv":3000000000,
        //                 "confirmations":13,
        //                 "type":"Deposit",
        //                 "status":"Success",
        //                 "createdAt":1592722565000
        //             }
        //         )
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        $response = yield $this->privateGetExchangeWalletsWithdrawList ($params);
        //
        //     {
        //         "$code":0,
        //         "msg":"OK",
        //         "$data":array(
        //             {
        //                 "address" => "1Lxxxxxxxxxxx"
        //                 "amountEv" => 200000
        //                 "$currency" => "BTC"
        //                 "currencyCode" => 1
        //                 "expiredTime" => 0
        //                 "feeEv" => 50000
        //                 "rejectReason" => null
        //                 "status" => "Succeed"
        //                 "txHash" => "44exxxxxxxxxxxxxxxxxxxxxx"
        //                 "withdrawStatus => ""
        //             }
        //         )
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            'Success' => 'ok',
            'Succeed' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // withdraw
        //
        //     ...
        //
        // fetchDeposits
        //
        //     {
        //         "$id":29200,
        //         "$currency":"USDT",
        //         "currencyCode":3,
        //         "txHash":"0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d",
        //         "$address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad",
        //         "amountEv":3000000000,
        //         "confirmations":13,
        //         "$type":"Deposit",
        //         "$status":"Success",
        //         "createdAt":1592722565000
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "$address" => "1Lxxxxxxxxxxx"
        //         "amountEv" => 200000
        //         "$currency" => "BTC"
        //         "currencyCode" => 1
        //         "expiredTime" => 0
        //         "feeEv" => 50000
        //         "rejectReason" => null
        //         "$status" => "Succeed"
        //         "txHash" => "44exxxxxxxxxxxxxxxxxxxxxx"
        //         "withdrawStatus => ""
        //     }
        //
        $id = $this->safe_string($transaction, 'id');
        $address = $this->safe_string($transaction, 'address');
        $tag = null;
        $txid = $this->safe_string($transaction, 'txHash');
        $currencyId = $this->safe_string($transaction, 'currency');
        $currency = $this->safe_currency($currencyId, $currency);
        $code = $currency['code'];
        $timestamp = $this->safe_integer_2($transaction, 'createdAt', 'submitedAt');
        $type = $this->safe_string_lower($transaction, 'type');
        $feeCost = $this->from_en($this->safe_float($transaction, 'feeEv'), $currency['valueScale'], $currency['precision']);
        $fee = null;
        if ($feeCost !== null) {
            $type = 'withdrawal';
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $amount = $this->from_en($this->safe_float($transaction, 'amountEv'), $currency['valueScale'], $currency['precision']);
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'address' => $address,
            'addressTo' => $address,
            'addressFrom' => null,
            'tag' => $tag,
            'tagTo' => $tag,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'fee' => $fee,
        );
    }

    public function fetch_positions($symbols = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $code = $this->safe_string($params, 'code');
        $request = array();
        if ($code === null) {
            $currencyId = $this->safe_string($params, 'currency');
            if ($currencyId === null) {
                throw new ArgumentsRequired($this->id . ' fetchPositions() requires a $currency parameter or a $code parameter');
            }
        } else {
            $currency = $this->currency($code);
            $params = $this->omit($params, 'code');
            $request['currency'] = $currency['id'];
        }
        $response = yield $this->privateGetAccountsAccountPositions (array_merge($request, $params));
        //
        //     {
        //         "$code":0,"msg":"",
        //         "$data":{
        //             "account":array(
        //                 "accountId":6192120001,
        //                 "$currency":"BTC",
        //                 "accountBalanceEv":1254744,
        //                 "totalUsedBalanceEv":0,
        //                 "bonusBalanceEv":1254744
        //             ),
        //             "$positions":array(
        //                 {
        //                     "accountID":6192120001,
        //                     "symbol":"BTCUSD",
        //                     "$currency":"BTC",
        //                     "side":"None",
        //                     "positionStatus":"Normal",
        //                     "crossMargin":false,
        //                     "leverageEr":100000000,
        //                     "leverage":1.00000000,
        //                     "initMarginReqEr":100000000,
        //                     "initMarginReq":1.00000000,
        //                     "maintMarginReqEr":500000,
        //                     "maintMarginReq":0.00500000,
        //                     "riskLimitEv":10000000000,
        //                     "riskLimit":100.00000000,
        //                     "size":0,
        //                     "value":0E-8,
        //                     "valueEv":0,
        //                     "avgEntryPriceEp":0,
        //                     "avgEntryPrice":0E-8,
        //                     "posCostEv":0,
        //                     "posCost":0E-8,
        //                     "assignedPosBalanceEv":0,
        //                     "assignedPosBalance":0E-8,
        //                     "bankruptCommEv":0,
        //                     "bankruptComm":0E-8,
        //                     "bankruptPriceEp":0,
        //                     "bankruptPrice":0E-8,
        //                     "positionMarginEv":0,
        //                     "positionMargin":0E-8,
        //                     "liquidationPriceEp":0,
        //                     "liquidationPrice":0E-8,
        //                     "deleveragePercentileEr":0,
        //                     "deleveragePercentile":0E-8,
        //                     "buyValueToCostEr":100225000,
        //                     "buyValueToCost":1.00225000,
        //                     "sellValueToCostEr":100075000,
        //                     "sellValueToCost":1.00075000,
        //                     "markPriceEp":135736070,
        //                     "markPrice":13573.60700000,
        //                     "markValueEv":0,
        //                     "markValue":null,
        //                     "unRealisedPosLossEv":0,
        //                     "unRealisedPosLoss":null,
        //                     "estimatedOrdLossEv":0,
        //                     "estimatedOrdLoss":0E-8,
        //                     "usedBalanceEv":0,
        //                     "usedBalance":0E-8,
        //                     "takeProfitEp":0,
        //                     "takeProfit":null,
        //                     "stopLossEp":0,
        //                     "stopLoss":null,
        //                     "cumClosedPnlEv":0,
        //                     "cumFundingFeeEv":0,
        //                     "cumTransactFeeEv":0,
        //                     "realisedPnlEv":0,
        //                     "realisedPnl":null,
        //                     "cumRealisedPnlEv":0,
        //                     "cumRealisedPnl":null
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $positions = $this->safe_value($data, 'positions', array());
        // todo unify parsePosition/parsePositions
        return $positions;
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $query = $this->omit($params, $this->extract_params($path));
        $requestPath = '/' . $this->implode_params($path, $params);
        $url = $requestPath;
        $queryString = '';
        if (($method === 'GET') || ($method === 'DELETE') || ($method === 'PUT')) {
            if ($query) {
                $queryString = $this->urlencode_with_array_repeat($query);
                $url .= '?' . $queryString;
            }
        }
        if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = $this->seconds();
            $xPhemexRequestExpiry = $this->safe_integer($this->options, 'x-phemex-request-expiry', 60);
            $expiry = $this->sum($timestamp, $xPhemexRequestExpiry);
            $expiryString = (string) $expiry;
            $headers = array(
                'x-phemex-access-token' => $this->apiKey,
                'x-phemex-request-expiry' => $expiryString,
            );
            $payload = '';
            if ($method === 'POST') {
                $payload = $this->json($params);
                $body = $payload;
                $headers['Content-Type'] = 'application/json';
            }
            $auth = $requestPath . $queryString . $expiryString . $payload;
            $headers['x-phemex-request-signature'] = $this->hmac($this->encode($auth), $this->encode($this->secret));
        }
        $url = $this->urls['api'][$api] . $url;
        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
        }
        //
        //     array("code":30018,"msg":"phemex.data.size.uplimt","data":null)
        //     array("code":412,"msg":"Missing parameter - resolution","data":null)
        //     array("code":412,"msg":"Missing parameter - to","data":null)
        //     array("$error":array("code":6001,"$message":"invalid argument"),"id":null,"result":null)
        //
        $error = $this->safe_value($response, 'error', $response);
        $errorCode = $this->safe_string($error, 'code');
        $message = $this->safe_string($error, 'msg');
        if (($errorCode !== null) && ($errorCode !== '0')) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
    }
}
