<?php

namespace ccxt;

// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

use Exception; // a common import
use ccxt\abstract\phemex as Exchange;

class phemex extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'phemex',
            'name' => 'Phemex',
            'countries' => array( 'CN' ), // China
            'rateLimit' => 120.5,
            'version' => 'v1',
            'certified' => false,
            'pro' => true,
            'hostname' => 'api.phemex.com',
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'closePosition' => false,
                'createConvertTrade' => true,
                'createOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchClosedOrders' => true,
                'fetchConvertQuote' => true,
                'fetchConvertTrade' => false,
                'fetchConvertTradeHistory' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => true,
                'fetchFundingRateHistories' => false,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => true,
                'fetchMarketLeverageTiers' => 'emulated',
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchPositions' => true,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTransfers' => true,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'sandbox' => true,
                'setLeverage' => true,
                'setMargin' => true,
                'setMarginMode' => true,
                'setPositionMode' => true,
                'transfer' => true,
                'withdraw' => 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',
                    'v2' => 'https://testnet-api.phemex.com',
                    'public' => 'https://testnet-api.phemex.com/exchange/public',
                    'private' => 'https://testnet-api.phemex.com',
                ),
                'api' => array(
                    'v1' => 'https://{hostname}/v1',
                    'v2' => 'https://{hostname}',
                    'public' => 'https://{hostname}/exchange/public',
                    'private' => 'https://{hostname}',
                ),
                'www' => 'https://phemex.com',
                'doc' => 'https://phemex-docs.github.io/#overview',
                'fees' => 'https://phemex.com/fees-conditions',
                'referral' => array(
                    'url' => 'https://phemex.com/register?referralCode=EDNVJ',
                    'discount' => 0.1,
                ),
            ),
            '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',
                '3M' => '7776000',
                '1Y' => '31104000',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'cfg/v2/products' => 5, // spot . contracts
                        'cfg/fundingRates' => 5,
                        'products' => 5, // contracts only
                        'nomics/trades' => 5, // ?market=<symbol>&since=<since>
                        'md/kline' => 5, // ?from=1589811875&resolution=1800&symbol=sBTCUSDT&to=1592457935
                        'md/v2/kline/list' => 5, // perpetual api ?symbol=<symbol>&to=<to>&from=<from>&resolution=<resolution>
                        'md/v2/kline' => 5, // ?symbol=<symbol>&resolution=<resolution>&limit=<limit>
                        'md/v2/kline/last' => 5, // perpetual ?symbol=<symbol>&resolution=<resolution>&limit=<limit>
                        'md/orderbook' => 5, // ?symbol=<symbol>
                        'md/trade' => 5, // ?symbol=<symbol>
                        'md/spot/ticker/24hr' => 5, // ?symbol=<symbol>
                        'exchange/public/cfg/chain-settings' => 5, // ?currency=<currency>
                    ),
                ),
                'v1' => array(
                    'get' => array(
                        'md/fullbook' => 5, // ?symbol=<symbol>
                        'md/orderbook' => 5, // ?symbol=<symbol>
                        'md/trade' => 5, // ?symbol=<symbol>&id=<id>
                        'md/ticker/24hr' => 5, // ?symbol=<symbol>&id=<id>
                        'md/ticker/24hr/all' => 5, // ?id=<id>
                        'md/spot/ticker/24hr' => 5, // ?symbol=<symbol>&id=<id>
                        'md/spot/ticker/24hr/all' => 5, // ?symbol=<symbol>&id=<id>
                        'exchange/public/products' => 5, // contracts only
                        'api-data/public/data/funding-rate-history' => 5,
                    ),
                ),
                'v2' => array(
                    'get' => array(
                        'public/products' => 5,
                        'public/products-plus' => 5,
                        'md/v2/orderbook' => 5, // ?symbol=<symbol>&id=<id>
                        'md/v2/trade' => 5, // ?symbol=<symbol>&id=<id>
                        'md/v2/ticker/24hr' => 5, // ?symbol=<symbol>&id=<id>
                        'md/v2/ticker/24hr/all' => 5, // ?id=<id>
                        'api-data/public/data/funding-rate-history' => 5,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        // spot
                        'spot/orders/active' => 1, // ?symbol=<symbol>&orderID=<orderID>
                        // 'spot/orders/active' => 5, // ?symbol=<symbol>&clOrDID=<clOrdID>
                        'spot/orders' => 1, // ?symbol=<symbol>
                        'spot/wallets' => 5, // ?currency=<currency>
                        'exchange/spot/order' => 5, // ?symbol=<symbol>&ordStatus=<ordStatus5,orderStatus2>ordType=<ordType5,orderType2>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        'exchange/spot/order/trades' => 5, // ?symbol=<symbol>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        'exchange/order/v2/orderList' => 5, // ?symbol=<symbol>&currency=<currency>&ordStatus=<ordStatus>&ordType=<ordType>&start=<start>&end=<end>&offset=<offset>&limit=<limit>&withCount=<withCount></withCount>
                        'exchange/order/v2/tradingList' => 5, // ?symbol=<symbol>&currency=<currency>&execType=<execType>&offset=<offset>&limit=<limit>&withCount=<withCount>
                        // swap
                        'accounts/accountPositions' => 1, // ?currency=<currency>
                        'g-accounts/accountPositions' => 1, // ?currency=<currency>
                        'g-accounts/positions' => 25, // ?currency=<currency>
                        'api-data/futures/funding-fees' => 5, // ?symbol=<symbol>
                        'api-data/g-futures/funding-fees' => 5, // ?symbol=<symbol>
                        'api-data/futures/orders' => 5, // ?symbol=<symbol>
                        'api-data/g-futures/orders' => 5, // ?symbol=<symbol>
                        'api-data/futures/orders/by-order-id' => 5, // ?symbol=<symbol>
                        'api-data/g-futures/orders/by-order-id' => 5, // ?symbol=<symbol>
                        'api-data/futures/trades' => 5, // ?symbol=<symbol>
                        'api-data/g-futures/trades' => 5, // ?symbol=<symbol>
                        'api-data/futures/trading-fees' => 5, // ?symbol=<symbol>
                        'api-data/g-futures/trading-fees' => 5, // ?symbol=<symbol>
                        'api-data/futures/v2/tradeAccountDetail' => 5, // ?currency=<currecny>&type=<type>&limit=<limit>&offset=<offset>&start=<start>&end=<end>&withCount=<withCount>
                        'g-orders/activeList' => 1, // ?symbol=<symbol>
                        'orders/activeList' => 1, // ?symbol=<symbol>
                        'exchange/order/list' => 5, // ?symbol=<symbol>&start=<start>&end=<end>&offset=<offset>&limit=<limit>&ordStatus=<ordStatus>&withCount=<withCount>
                        'exchange/order' => 5, // ?symbol=<symbol>&orderID=<orderID5,orderID2>
                        // 'exchange/order' => 5, // ?symbol=<symbol>&clOrdID=<clOrdID5,clOrdID2>
                        'exchange/order/trade' => 5, // ?symbol=<symbol>&start=<start>&end=<end>&limit=<limit>&offset=<offset>&withCount=<withCount>
                        'phemex-user/users/children' => 5, // ?offset=<offset>&limit=<limit>&withCount=<withCount>
                        'phemex-user/wallets/v2/depositAddress' => 5, // ?_t=1592722635531&currency=USDT
                        'phemex-user/wallets/tradeAccountDetail' => 5, // ?bizCode=&currency=&end=1642443347321&limit=10&offset=0&side=&start=1&type=4&withCount=true
                        'phemex-deposit/wallets/api/depositAddress' => 5, // ?currency=<currency>&chainName=<chainName>
                        'phemex-deposit/wallets/api/depositHist' => 5, // ?currency=<currency>&offset=<offset>&limit=<limit>&withCount=<withCount>
                        'phemex-deposit/wallets/api/chainCfg' => 5, // ?currency=<currency>
                        'phemex-withdraw/wallets/api/withdrawHist' => 5, // ?currency=<currency>&chainName=<chainNameList>&offset=<offset>&limit=<limit>&withCount=<withCount>
                        'phemex-withdraw/wallets/api/asset/info' => 5, // ?currency=<currency>&amount=<amount>
                        'phemex-user/order/closedPositionList' => 5, // ?currency=USD&limit=10&offset=0&symbol=&withCount=true
                        'exchange/margins/transfer' => 5, // ?start=<start>&end=<end>&offset=<offset>&limit=<limit>&withCount=<withCount>
                        'exchange/wallets/confirm/withdraw' => 5, // ?code=<withdrawConfirmCode>
                        'exchange/wallets/withdrawList' => 5, // ?currency=<currency>&limit=<limit>&offset=<offset>&withCount=<withCount>
                        'exchange/wallets/depositList' => 5, // ?currency=<currency>&offset=<offset>&limit=<limit>
                        'exchange/wallets/v2/depositAddress' => 5, // ?currency=<currency>
                        'api-data/spots/funds' => 5, // ?currency=<currency>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        'api-data/spots/orders' => 5, // ?symbol=<symbol>
                        'api-data/spots/orders/by-order-id' => 5, // ?symbol=<symbol>&oderId=<orderID>&clOrdID=<clOrdID>
                        'api-data/spots/pnls' => 5,
                        'api-data/spots/trades' => 5, // ?symbol=<symbol>
                        'api-data/spots/trades/by-order-id' => 5, // ?symbol=<symbol>&oderId=<orderID>&clOrdID=<clOrdID>
                        'assets/convert' => 5, // ?startTime=<startTime>&endTime=<endTime>&limit=<limit>&offset=<offset>
                        // transfer
                        'assets/transfer' => 5, // ?currency=<currency>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        'assets/spots/sub-accounts/transfer' => 5, // ?currency=<currency>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        'assets/futures/sub-accounts/transfer' => 5, // ?currency=<currency>&start=<start>&end=<end>&limit=<limit>&offset=<offset>
                        'assets/quote' => 5, // ?fromCurrency=<currency>&toCurrency=<currency>&amountEv=<amount>
                        // deposit/withdraw
                    ),
                    'post' => array(
                        // spot
                        'spot/orders' => 1,
                        // swap
                        'orders' => 1,
                        'g-orders' => 1,
                        'positions/assign' => 5, // ?symbol=<symbol>&posBalance=<posBalance>&posBalanceEv=<posBalanceEv>
                        'exchange/wallets/transferOut' => 5,
                        'exchange/wallets/transferIn' => 5,
                        'exchange/margins' => 5,
                        'exchange/wallets/createWithdraw' => 5, // ?otpCode=<otpCode>
                        'exchange/wallets/cancelWithdraw' => 5,
                        'exchange/wallets/createWithdrawAddress' => 5, // ?otpCode={optCode}
                        // transfer
                        'assets/transfer' => 5,
                        'assets/spots/sub-accounts/transfer' => 5, // for sub-account only
                        'assets/futures/sub-accounts/transfer' => 5, // for sub-account only
                        'assets/universal-transfer' => 5, // for Main account only
                        'assets/convert' => 5,
                        // withdraw
                        'phemex-withdraw/wallets/api/createWithdraw' => 5, // ?currency=<currency>&address=<address>&amount=<amount>&addressTag=<addressTag>&chainName=<chainName>
                        'phemex-withdraw/wallets/api/cancelWithdraw' => 5, // ?id=<id>
                    ),
                    'put' => array(
                        // spot
                        'spot/orders/create' => 1, // ?symbol=<symbol>&trigger=<trigger>&clOrdID=<clOrdID>&priceEp=<priceEp>&baseQtyEv=<baseQtyEv>&quoteQtyEv=<quoteQtyEv>&stopPxEp=<stopPxEp>&text=<text>&side=<side>&qtyType=<qtyType>&ordType=<ordType>&timeInForce=<timeInForce>&execInst=<execInst>
                        'spot/orders' => 1, // ?symbol=<symbol>&orderID=<orderID>&origClOrdID=<origClOrdID>&clOrdID=<clOrdID>&priceEp=<priceEp>&baseQtyEV=<baseQtyEV>&quoteQtyEv=<quoteQtyEv>&stopPxEp=<stopPxEp>
                        // swap
                        'orders/replace' => 1, // ?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>
                        'g-orders/replace' => 1, // ?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>
                        'g-orders/create' => 1,
                        'positions/leverage' => 5, // ?symbol=<symbol>&leverage=<leverage>&leverageEr=<leverageEr>
                        'g-positions/leverage' => 5, // ?symbol=<symbol>&leverage=<leverage>&leverageEr=<leverageEr>
                        'g-positions/switch-pos-mode-sync' => 5, // ?symbol=<symbol>&targetPosMode=<targetPosMode>
                        'positions/riskLimit' => 5, // ?symbol=<symbol>&riskLimit=<riskLimit>&riskLimitEv=<riskLimitEv>
                    ),
                    'delete' => array(
                        // spot
                        'spot/orders' => 2, // ?symbol=<symbol>&orderID=<orderID>
                        'spot/orders/all' => 2, // ?symbol=<symbol>&untriggered=<untriggered>
                        // 'spot/orders' => 5, // ?symbol=<symbol>&clOrdID=<clOrdID>
                        // swap
                        'orders/cancel' => 1, // ?symbol=<symbol>&orderID=<orderID>
                        'orders' => 1, // ?symbol=<symbol>&orderID=<orderID1>,<orderID2>,<orderID3>
                        'orders/all' => 3, // ?symbol=<symbol>&untriggered=<untriggered>&text=<text>
                        'g-orders/cancel' => 1, // ?symbol=<symbol>&orderID=<orderID>
                        'g-orders' => 1, // ?symbol=<symbol>&orderID=<orderID1>,<orderID2>,<orderID3>
                        'g-orders/all' => 3, // ?symbol=<symbol>&untriggered=<untriggered>&text=<text>
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.001'),
                    'maker' => $this->parse_number('0.001'),
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        // todo
                        'triggerPriceType' => array(
                            'mark' => true,
                            'last' => true,
                            'index' => true,
                        ),
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'trailing' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 200,
                        'daysBack' => 100000,
                        'untilDays' => 2, // todo implement
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'daysBack' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 200,
                        'daysBack' => 100000,
                        'daysBackCanceled' => 100000,
                        'untilDays' => 2,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'forDerivatives' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        'triggerDirection' => true,
                        'attachedStopLossTakeProfit' => array(
                            'triggerPriceType' => array(
                                'mark' => true,
                                'last' => true,
                                'index' => true,
                            ),
                            'price' => true,
                        ),
                        'hedged' => true,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 2000,
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forDerivatives',
                    ),
                    'inverse' => array(
                        'extends' => 'forDerivatives',
                    ),
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
            ),
            'exceptions' => array(
                'exact' => array(
                    // not documented
                    '401' => '\\ccxt\\AuthenticationError', // array("code":"401","msg":"401 Failed to load API KEY.")
                    '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
                    '11116' => '\\ccxt\\InvalidOrder', // TE_BO_NUM_EXCEEDS Details => the total count of brakcet orders should equal or less than 5
                    '11117' => '\\ccxt\\InvalidOrder', // TE_BO_CANNOT_HAVE_BO_WITH_DIFF_SIDE Details => all bracket orders should have the same Side.
                    '11118' => '\\ccxt\\InvalidOrder', // TE_BO_TP_PRICE_INVALID Details => bracker order take profit price is invalid
                    '11119' => '\\ccxt\\InvalidOrder', // TE_BO_SL_PRICE_INVALID Details => bracker order stop loss price is invalid
                    '11120' => '\\ccxt\\InvalidOrder', // TE_BO_SL_TRIGGER_PRICE_INVALID Details => bracker order stop loss trigger price is invalid
                    '11121' => '\\ccxt\\InvalidOrder', // TE_BO_CANNOT_REPLACE Details => cannot replace bracket order.
                    '11122' => '\\ccxt\\InvalidOrder', // TE_BO_BOTP_STATUS_INVALID Details => bracket take profit order status is invalid
                    '11123' => '\\ccxt\\InvalidOrder', // TE_BO_CANNOT_PLACE_BOTP_OR_BOSL_ORDER Details => cannot place bracket take profit order
                    '11124' => '\\ccxt\\InvalidOrder', // TE_BO_CANNOT_REPLACE_BOTP_OR_BOSL_ORDER Details => cannot place bracket stop loss order
                    '11125' => '\\ccxt\\InvalidOrder', // TE_BO_CANNOT_CANCEL_BOTP_OR_BOSL_ORDER Details => cannot cancel bracket sl/tp order
                    '11126' => '\\ccxt\\InvalidOrder', // TE_BO_DONOT_SUPPORT_API Details => doesn't support bracket order via API
                    '11128' => '\\ccxt\\InvalidOrder', // TE_BO_INVALID_EXECINST Details => ExecInst value is invalid
                    '11129' => '\\ccxt\\InvalidOrder', // TE_BO_MUST_BE_SAME_SIDE_AS_POS Details => bracket order should have the same side's side
                    '11130' => '\\ccxt\\InvalidOrder', // TE_BO_WRONG_SL_TRIGGER_TYPE Details => bracket stop loss order trigger type is invalid
                    '11131' => '\\ccxt\\InvalidOrder', // TE_BO_WRONG_TP_TRIGGER_TYPE Details => bracket take profit order trigger type is invalid
                    '11132' => '\\ccxt\\InvalidOrder', // TE_BO_ABORT_BOSL_DUE_BOTP_CREATE_FAILED Details => cancel bracket stop loss order due failed to create take profit order.
                    '11133' => '\\ccxt\\InvalidOrder', // TE_BO_ABORT_BOSL_DUE_BOPO_CANCELED Details => cancel bracket stop loss order due main order canceled.
                    '11134' => '\\ccxt\\InvalidOrder', // TE_BO_ABORT_BOTP_DUE_BOPO_CANCELED Details => cancel bracket take profit order due main order canceled.
                    // not documented
                    '30000' => '\\ccxt\\BadRequest', // array("code":30000,"msg":"Please double check input arguments","data":null)
                    '30018' => '\\ccxt\\BadRequest', // array("code":30018,"msg":"phemex.data.size.uplimt","data":null)
                    '34003' => '\\ccxt\\PermissionDenied', // array("code":34003,"msg":"Access forbidden","data":null)
                    '35104' => '\\ccxt\\InsufficientFunds', // array("code":35104,"msg":"phemex.spot.wallet.balance.notenough","data":null)
                    '39995' => '\\ccxt\\RateLimitExceeded', // array("code" => "39995","msg" => "Too many requests.")
                    '39996' => '\\ccxt\\PermissionDenied', // array("code" => "39996","msg" => "Access denied.")
                    '39997' => '\\ccxt\\BadSymbol', // array("code":39997,"msg":"Symbol not listed sMOVRUSDT","data":null)
                ),
                'broad' => array(
                    '401 Insufficient privilege' => '\\ccxt\\PermissionDenied', // array("code" => "401","msg" => "401 Insufficient privilege.")
                    '401 Request IP mismatch' => '\\ccxt\\PermissionDenied', // array("code" => "401","msg" => "401 Request IP mismatch.")
                    '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)
                    'Api key not found' => '\\ccxt\\AuthenticationError', // array("msg":"Api key not found 698dc9e3-6faa-4910-9476-12857e79e198","code":"10500")
                ),
            ),
            'options' => array(
                'brokerId' => 'CCXT123456', // updated from CCXT to CCXT123456
                'x-phemex-request-expiry' => 60, // in seconds
                'createOrderByQuoteRequiresPrice' => true,
                'networks' => array(
                    'TRC20' => 'TRX',
                    'ERC20' => 'ETH',
                    'BEP20' => 'BNB',
                ),
                'defaultNetworks' => array(
                    'USDT' => 'ETH',
                    'MKR' => 'ETH',
                ),
                'defaultSubType' => 'linear',
                'accountsByType' => array(
                    'spot' => 'spot',
                    'swap' => 'future',
                ),
                'stableCoins' => array(
                    'BUSD',
                    'FEI',
                    'TUSD',
                    'USD',
                    'USDC',
                    'USDD',
                    'USDP',
                    'USDT',
                ),
                'transfer' => array(
                    'fillResponseFromRequest' => true,
                ),
                'triggerPriceTypesMap' => array(
                    'last' => 'ByLastPrice',
                    'mark' => 'ByMarkPrice',
                    'index' => 'ByIndexPrice',
                    'ask' => 'ByAskPrice',
                    'bid' => 'ByBidPrice',
                ),
            ),
        ));
    }

    public function parse_safe_number($value = null) {
        if ($value === null) {
            return $value;
        }
        $parts = explode(',', $value);
        $value = implode('', $parts);
        $parts = explode(' ', $value);
        return $this->safe_number($parts, 0);
    }

    public function parse_swap_market(array $market) {
        //
        //     {
        //         "symbol":"BTCUSD", //
        //         "code":"1",
        //         "type":"Perpetual",
        //         "displaySymbol":"BTC / USD",
        //         "indexSymbol":".BTC",
        //         "markSymbol":".MBTC",
        //         "fundingRateSymbol":".BTCFR",
        //         "fundingRate8hSymbol":".BTCFR8H",
        //         "contractUnderlyingAssets":"USD", // or eg. `1000 SHIB`
        //         "settleCurrency":"BTC",
        //         "quoteCurrency":"USD",
        //         "contractSize":"1 USD",
        //         "lotSize":1,
        //         "tickSize":0.5,
        //         "priceScale":4,
        //         "ratioScale":8,
        //         "pricePrecision":1,
        //         "minPriceEp":5000,
        //         "maxPriceEp":10000000000,
        //         "maxOrderQty":1000000,
        //         "status":"Listed",
        //         "tipOrderQty":1000000,
        //         "listTime":"1574650800000",
        //         "majorSymbol":true,
        //         "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');
        $contractUnderlyingAssets = $this->safe_string($market, 'contractUnderlyingAssets');
        $baseId = $this->safe_string($market, 'baseCurrency', $contractUnderlyingAssets);
        $quoteId = $this->safe_string($market, 'quoteCurrency');
        $settleId = $this->safe_string($market, 'settleCurrency');
        $base = $this->safe_currency_code($baseId);
        $base = str_replace(' ', '', $base); // replace space for junction codes, eg. `1000 SHIB`
        $quote = $this->safe_currency_code($quoteId);
        $settle = $this->safe_currency_code($settleId);
        $inverse = false;
        if ($settleId !== $quoteId) {
            $inverse = true;
            // some unhandled cases
            if (!(is_array($market) && array_key_exists('baseCurrency', $market)) && $base === $quote) {
                $base = $settle;
            }
        }
        $priceScale = $this->safe_integer($market, 'priceScale');
        $ratioScale = $this->safe_integer($market, 'ratioScale');
        $valueScale = $this->safe_integer($market, 'valueScale');
        $minPriceEp = $this->safe_string($market, 'minPriceEp');
        $maxPriceEp = $this->safe_string($market, 'maxPriceEp');
        $makerFeeRateEr = $this->safe_string($market, 'makerFeeRateEr');
        $takerFeeRateEr = $this->safe_string($market, 'takerFeeRateEr');
        $status = $this->safe_string($market, 'status');
        $contractSizeString = $this->safe_string($market, 'contractSize', ' ');
        $contractSize = null;
        if ($settle === 'USDT') {
            $contractSize = $this->parse_number('1');
        } elseif (mb_strpos($contractSizeString, ' ')) {
            // "1 USD"
            // "0.005 ETH"
            $parts = explode(' ', $contractSizeString);
            $contractSize = $this->parse_number($parts[0]);
        } else {
            // "1.0"
            $contractSize = $this->parse_number($contractSizeString);
        }
        return $this->safe_market_structure(array(
            'id' => $id,
            'symbol' => $base . '/' . $quote . ':' . $settle,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => 'swap',
            'spot' => false,
            'margin' => false,
            'swap' => true,
            'future' => false,
            'option' => false,
            'active' => $status === 'Listed',
            'contract' => true,
            'linear' => !$inverse,
            'inverse' => $inverse,
            'taker' => $this->parse_number($this->from_en($takerFeeRateEr, $ratioScale)),
            'maker' => $this->parse_number($this->from_en($makerFeeRateEr, $ratioScale)),
            'contractSize' => $contractSize,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'priceScale' => $priceScale,
            'valueScale' => $valueScale,
            'ratioScale' => $ratioScale,
            'precision' => array(
                'amount' => $this->safe_number_2($market, 'lotSize', 'qtyStepSize'),
                'price' => $this->safe_number($market, 'tickSize'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => $this->parse_number('1'),
                    'max' => $this->safe_number($market, 'maxLeverage'),
                ),
                'amount' => array(
                    'min' => null,
                    'max' => null,
                ),
                'price' => array(
                    'min' => $this->parse_number($this->from_en($minPriceEp, $priceScale)),
                    'max' => $this->parse_number($this->from_en($maxPriceEp, $priceScale)),
                ),
                'cost' => array(
                    'min' => null,
                    'max' => $this->parse_number($this->safe_string($market, 'maxOrderQty')),
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function parse_spot_market(array $market) {
        //
        //     array(
        //         "symbol":"sBTCUSDT",
        //         "code":1001,
        //         "type":"Spot",
        //         "displaySymbol":"BTC / USDT",
        //         "quoteCurrency":"USDT",
        //         "priceScale":8,
        //         "ratioScale":8,
        //         "pricePrecision":2,
        //         "baseCurrency":"BTC",
        //         "baseTickSize":"0.000001 BTC",
        //         "baseTickSizeEv":100,
        //         "quoteTickSize":"0.01 USDT",
        //         "quoteTickSizeEv":1000000,
        //         "baseQtyPrecision":6,
        //         "quoteQtyPrecision":2,
        //         "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,
        //         "description":"BTCUSDT is a BTC/USDT spot trading pair. Minimum order value is 1 USDT",
        //         "status":"Listed",
        //         "tipOrderQty":2,
        //         "listTime":1589338800000,
        //         "buyPriceUpperLimitPct":110,
        //         "sellPriceLowerLimitPct":90,
        //         "leverage":5
        //     ),
        //
        $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');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $status = $this->safe_string($market, 'status');
        $precisionAmount = $this->parse_safe_number($this->safe_string($market, 'baseTickSize'));
        $precisionPrice = $this->parse_safe_number($this->safe_string($market, 'quoteTickSize'));
        return $this->safe_market_structure(array(
            'id' => $id,
            'symbol' => $base . '/' . $quote,
            'base' => $base,
            'quote' => $quote,
            'settle' => null,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => null,
            'type' => $type,
            'spot' => true,
            'margin' => false,
            'swap' => false,
            'future' => false,
            'option' => false,
            'active' => $status === 'Listed',
            'contract' => false,
            'linear' => null,
            'inverse' => null,
            'taker' => $this->safe_number($market, 'defaultTakerFee'),
            'maker' => $this->safe_number($market, 'defaultMakerFee'),
            'contractSize' => null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'priceScale' => $this->safe_integer($market, 'priceScale'),
            'valueScale' => $this->safe_integer($market, 'valueScale'),
            'ratioScale' => $this->safe_integer($market, 'ratioScale'),
            'precision' => array(
                'amount' => $precisionAmount,
                'price' => $precisionPrice,
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $precisionAmount,
                    'max' => $this->parse_safe_number($this->safe_string($market, 'maxBaseOrderSize')),
                ),
                'price' => array(
                    'min' => $precisionPrice,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->parse_safe_number($this->safe_string($market, 'minOrderValue')),
                    'max' => $this->parse_safe_number($this->safe_string($market, 'maxOrderValue')),
                ),
            ),
            'created' => $this->safe_integer($market, 'listTime'),
            'info' => $market,
        ));
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all markets for phemex
         *
         * @see https://phemex-docs.github.io/#query-product-information-3
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market data
         */
        $v2ProductsPromise = $this->v2GetPublicProducts ($params);
        //
        //     {
        //         "code":0,
        //         "msg":"",
        //         "data":{
        //             "currencies":array(
        //                 array("currency":"BTC","name":"Bitcoin","code":1,"valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"needAddrTag":0,"status":"Listed","displayCurrency":"BTC","inAssetsDisplay":1,"perpetual":0,"stableCoin":0,"assetsPrecision":8),
        //                 array("currency":"USD","name":"USD","code":2,"valueScale":4,"minValueEv":1,"maxValueEv":5000000000000000000,"needAddrTag":0,"status":"Listed","displayCurrency":"USD","inAssetsDisplay":1,"perpetual":0,"stableCoin":0,"assetsPrecision":2),
        //                 array("currency":"USDT","name":"TetherUS","code":3,"valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"needAddrTag":0,"status":"Listed","displayCurrency":"USDT","inAssetsDisplay":1,"perpetual":2,"stableCoin":1,"assetsPrecision":8),
        //             ),
        //             "products":array(
        //                 array(
        //                     "symbol":"BTCUSD",
        //                     "code":1,
        //                     "type":"Perpetual"
        //                     "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,
        //                     "description":"BTC/USD perpetual contracts are priced on the .BTC Index. Each contract is worth 1 USD. Funding fees are paid and received every 8 hours at UTC time => 00:00, 08:00 and 16:00.",
        //                     "status":"Listed",
        //                     "tipOrderQty":1000000,
        //                     "listTime":1574650800000,
        //                     "majorSymbol":true,
        //                     "defaultLeverage":"-10",
        //                     "fundingInterval":28800,
        //                     "maxLeverage":100
        //                 ),
        //                 array(
        //                     "symbol":"sBTCUSDT",
        //                     "code":1001,
        //                     "type":"Spot",
        //                     "displaySymbol":"BTC / USDT",
        //                     "quoteCurrency":"USDT",
        //                     "priceScale":8,
        //                     "ratioScale":8,
        //                     "pricePrecision":2,
        //                     "baseCurrency":"BTC",
        //                     "baseTickSize":"0.000001 BTC",
        //                     "baseTickSizeEv":100,
        //                     "quoteTickSize":"0.01 USDT",
        //                     "quoteTickSizeEv":1000000,
        //                     "baseQtyPrecision":6,
        //                     "quoteQtyPrecision":2,
        //                     "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,
        //                     "description":"BTCUSDT is a BTC/USDT spot trading pair. Minimum order value is 1 USDT",
        //                     "status":"Listed",
        //                     "tipOrderQty":2,
        //                     "listTime":1589338800000,
        //                     "buyPriceUpperLimitPct":110,
        //                     "sellPriceLowerLimitPct":90,
        //                     "leverage":5
        //                 ),
        //             ),
        //             "perpProductsV2":array(
        //                 array(
        //                     "symbol":"BTCUSDT",
        //                     "code":41541,
        //                     "type":"PerpetualV2",
        //                     "displaySymbol":"BTC / USDT",
        //                     "indexSymbol":".BTCUSDT",
        //                     "markSymbol":".MBTCUSDT",
        //                     "fundingRateSymbol":".BTCUSDTFR",
        //                     "fundingRate8hSymbol":".BTCUSDTFR8H",
        //                     "contractUnderlyingAssets":"BTC",
        //                     "settleCurrency":"USDT",
        //                     "quoteCurrency":"USDT",
        //                     "tickSize":"0.1",
        //                     "priceScale":0,
        //                     "ratioScale":0,
        //                     "pricePrecision":1,
        //                     "baseCurrency":"BTC",
        //                     "description":"BTC/USDT perpetual contracts are priced on the .BTCUSDT Index. Each contract is worth 1 BTC. Funding fees are paid and received every 8 hours at UTC time => 00:00, 08:00 and 16:00.",
        //                     "status":"Listed",
        //                     "tipOrderQty":0,
        //                     "listTime":1668225600000,
        //                     "majorSymbol":true,
        //                     "defaultLeverage":"-10",
        //                     "fundingInterval":28800,
        //                     "maxLeverage":100,
        //                     "maxOrderQtyRq":"1000",
        //                     "maxPriceRp":"2000000000",
        //                     "minOrderValueRv":"1",
        //                     "minPriceRp":"1000.0",
        //                     "qtyPrecision":3,
        //                     "qtyStepSize":"0.001",
        //                     "tipOrderQtyRq":"200",
        //                     "maxOpenPosLeverage":100.0
        //                 ),
        //             ),
        //             "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]),
        //             ],
        //             "riskLimitsV2":array(
        //                 array(
        //                     "symbol":"BTCUSDT",
        //                     "steps":"2000K",
        //                     "riskLimits":array(
        //                         array("limit":2000000,"initialMarginRr":"0.01","maintenanceMarginRr":"0.005"),,
        //                         array("limit":4000000,"initialMarginRr":"0.015","maintenanceMarginRr":"0.0075"),
        //                         array("limit":6000000,"initialMarginRr":"0.02","maintenanceMarginRr":"0.01"),
        //                     )
        //                 ),
        //             ),
        //             "leveragesV2":[
        //                 array("options":[1.0,2.0,3.0,5.0,10.0,25.0,50.0,100.0],"initialMarginRr":"0.01"),
        //                 array("options":[1.0,2.0,3.0,5.0,10.0,25.0,50.0,66.67],"initialMarginRr":"0.015"),
        //                 array("options":[1.0,2.0,3.0,5.0,10.0,25.0,33.0,50.0],"initialMarginRr":"0.02"),
        //             ],
        //             "ratioScale":8,
        //             "md5Checksum":"5c6604814d3c1bafbe602c3d11a7e8bf",
        //         }
        //     }
        //
        $v1ProductsPromise = $this->v1GetExchangePublicProducts ($params);
        list($v2Products, $v1Products) = array( $v2ProductsPromise, $v1ProductsPromise );
        $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_dict($v2Products, 'data', array());
        $products = $this->safe_list($v2ProductsData, 'products', array());
        $perpetualProductsV2 = $this->safe_list($v2ProductsData, 'perpProductsV2', array());
        $products = $this->array_concat($products, $perpetualProductsV2);
        $riskLimits = $this->safe_list($v2ProductsData, 'riskLimits', array());
        $riskLimitsV2 = $this->safe_list($v2ProductsData, 'riskLimitsV2', array());
        $riskLimits = $this->array_concat($riskLimits, $riskLimitsV2);
        $currencies = $this->safe_list($v2ProductsData, 'currencies', array());
        $riskLimitsById = $this->index_by($riskLimits, 'symbol');
        $v1ProductsById = $this->index_by($v1ProductsData, 'symbol');
        $currenciesByCode = $this->index_by($currencies, 'currency');
        $result = array();
        for ($i = 0; $i < count($products); $i++) {
            $market = $products[$i];
            $type = $this->safe_string_lower($market, 'type');
            if (($type === 'perpetual') || ($type === 'perpetualv2') || ($type === 'perpetualpilot')) {
                $id = $this->safe_string($market, 'symbol');
                $riskLimitValues = $this->safe_dict($riskLimitsById, $id, array());
                $market = $this->extend($market, $riskLimitValues);
                $v1ProductsValues = $this->safe_dict($v1ProductsById, $id, array());
                $market = $this->extend($market, $v1ProductsValues);
                $market = $this->parse_swap_market($market);
            } else {
                $baseCurrency = $this->safe_string($market, 'baseCurrency');
                $currencyValues = $this->safe_dict($currenciesByCode, $baseCurrency, array());
                $valueScale = $this->safe_string($currencyValues, 'valueScale', '8');
                $market = $this->extend($market, array( 'valueScale' => $valueScale ));
                $market = $this->parse_spot_market($market);
            }
            $result[] = $market;
        }
        return $result;
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available $currencies on an exchange
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of $currencies
         */
        $response = $this->v2GetPublicProducts ($params);
        //
        //     {
        //         "code":0,
        //         "msg":"OK",
        //         "data":{
        //             ...,
        //             "currencies":array(
        //                 array("currency":"BTC","name":"Bitcoin","code":1,"valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"needAddrTag":0,"status":"Listed","displayCurrency":"BTC","inAssetsDisplay":1,"perpetual":0,"stableCoin":0,"assetsPrecision":8),
        //                 array("currency":"USD","name":"USD","code":2,"valueScale":4,"minValueEv":1,"maxValueEv":5000000000000000000,"needAddrTag":0,"status":"Listed","displayCurrency":"USD","inAssetsDisplay":1,"perpetual":0,"stableCoin":0,"assetsPrecision":2),
        //                 array("currency":"USDT","name":"TetherUS","code":3,"valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"needAddrTag":0,"status":"Listed","displayCurrency":"USDT","inAssetsDisplay":1,"perpetual":2,"stableCoin":1,"assetsPrecision":8),
        //             ),
        //             ...
        //         }
        //     }
        $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');
            $code = $this->safe_currency_code($id);
            $valueScaleString = $this->safe_string($currency, 'valueScale');
            $valueScale = intval($valueScaleString);
            $minValueEv = $this->safe_string($currency, 'minValueEv');
            $maxValueEv = $this->safe_string($currency, 'maxValueEv');
            $minAmount = null;
            $maxAmount = null;
            $precision = null;
            if ($valueScale !== null) {
                $precisionString = $this->parse_precision($valueScaleString);
                $precision = $this->parse_number($precisionString);
                $minAmount = $this->parse_number(Precise::string_mul($minValueEv, $precisionString));
                $maxAmount = $this->parse_number(Precise::string_mul($maxValueEv, $precisionString));
            }
            $result[$code] = $this->safe_currency_structure(array(
                'id' => $id,
                'info' => $currency,
                'code' => $code,
                'name' => $this->safe_string($currency, 'name'),
                'active' => $this->safe_string($currency, 'status') === 'Listed',
                'deposit' => null,
                'withdraw' => null,
                'fee' => null,
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => $minAmount,
                        'max' => $maxAmount,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'valueScale' => $valueScale,
                'networks' => null,
                'type' => 'crypto',
            ));
        }
        return $result;
    }

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

    public function custom_parse_order_book($orderbook, $symbol, $timestamp = null, $bidsKey = 'bids', $asksKey = 'asks', $priceKey = 0, $amountKey = 1, ?array $market = null) {
        $result = array(
            'symbol' => $symbol,
            '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->custom_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(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryorderbook
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the order $book for
         * @param {int} [$limit] the maximum amount of order $book entries to return
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-$book-structure order $book structures~ indexed by $market symbols
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'id' => 123456789, // optional $request id
        );
        $response = null;
        $isStableSettled = ($market['settle'] === 'USDT') || ($market['settle'] === 'USDC');
        if ($market['linear'] && $isStableSettled) {
            $response = $this->v2GetMdV2Orderbook ($this->extend($request, $params));
        } else {
            if (($limit !== null) && ($limit <= 30)) {
                $response = $this->v1GetMdOrderbook ($this->extend($request, $params));
            } else {
                $response = $this->v1GetMdFullbook ($this->extend($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_2($result, 'book', 'orderbook_p', array());
        $timestamp = $this->safe_integer_product($result, 'timestamp', 0.000001);
        $orderbook = $this->custom_parse_order_book($book, $symbol, $timestamp, 'bids', 'asks', 0, 1, $market);
        $orderbook['nonce'] = $this->safe_integer($result, 'sequence');
        return $orderbook;
    }

    public function to_en($n, $scale) {
        $stringN = $this->number_to_string($n);
        $precise = new Precise ($stringN);
        $precise->decimals = $precise->decimals - $scale;
        $precise->reduce ();
        $preciseString = (string) $precise;
        return $this->parse_to_numeric($preciseString);
    }

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

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

    public function from_en($en, $scale) {
        if ($en === null || $scale === null) {
            return null;
        }
        $precise = new Precise ($en);
        $precise->decimals = $this->sum($precise->decimals, $scale);
        $precise->reduce ();
        return (string) $precise;
    }

    public function from_ep($ep, ?array $market = null) {
        if (($ep === null) || ($market === null)) {
            return $ep;
        }
        return $this->from_en($ep, $this->safe_integer($market, 'priceScale'));
    }

    public function from_ev($ev, ?array $market = null) {
        if (($ev === null) || ($market === null)) {
            return $ev;
        }
        return $this->from_en($ev, $this->safe_integer($market, 'valueScale'));
    }

    public function from_er($er, ?array $market = null) {
        if (($er === null) || ($market === null)) {
            return $er;
        }
        return $this->from_en($er, $this->safe_integer($market, 'ratioScale'));
    }

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

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick $data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#querykline
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#query-kline
         *
         * @param {string} $symbol unified $symbol of the $market $to fetch OHLCV $data for
         * @param {string} $timeframe the length of time each candle represents
         * @param {int} [$since] *only used for USDT settled contracts, otherwise is emulated and not supported by the exchange* timestamp in ms of the earliest candle $to fetch
         * @param {int} [$limit] the maximum amount of candles $to fetch
         * @param {array} [$params] extra parameters specific $to the exchange API endpoint
         * @param {int} [$params->until] *USDT settled/ linear swaps only* end time in ms
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $userLimit = $limit;
        $request = array(
            'symbol' => $market['id'],
            'resolution' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
        );
        $until = $this->safe_integer_2($params, 'until', 'to');
        $params = $this->omit($params, array( 'until' ));
        $isStableSettled = ($market['settle'] === 'USDT') || ($market['settle'] === 'USDC');
        $usesSpecialFromToEndpoint = (($market['linear'] || $isStableSettled)) && (($since !== null) || ($until !== null));
        $maxLimit = 1000;
        if ($usesSpecialFromToEndpoint) {
            $maxLimit = 2000;
        }
        if ($limit === null) {
            $limit = $maxLimit;
        }
        $request['limit'] = min ($limit, $maxLimit);
        $response = null;
        if ($market['linear'] || $isStableSettled) {
            if (($until !== null) || ($since !== null)) {
                $candleDuration = $this->parse_timeframe($timeframe);
                if ($since !== null) {
                    $since = (int) round($since / 1000);
                    $request['from'] = $since;
                } else {
                    // when 'to' is defined $since is mandatory
                    $since = ($until / 100) - ($maxLimit * $candleDuration);
                }
                if ($until !== null) {
                    $request['to'] = (int) round($until / 1000);
                } else {
                    // when $since is defined 'to' is mandatory
                    $to = $since . ($maxLimit * $candleDuration);
                    $now = $this->seconds();
                    if ($to > $now) {
                        $to = $now;
                    }
                    $request['to'] = $to;
                }
                $response = $this->publicGetMdV2KlineList ($this->extend($request, $params));
            } else {
                $response = $this->publicGetMdV2KlineLast ($this->extend($request, $params));
            }
        } else {
            if ($since !== null) {
                // phemex also provides kline query with from/to, however, this interface is NOT recommended and does not work properly.
                // we do not send $since param $to the exchange, instead we calculate appropriate $limit param
                $duration = $this->parse_timeframe($timeframe) * 1000;
                $timeDelta = $this->milliseconds() - $since;
                $limit = $this->parse_to_int($timeDelta / $duration); // setting $limit $to the number of candles after $since
            }
            $response = $this->publicGetMdV2Kline ($this->extend($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_list($data, 'rows', array());
        return $this->parse_ohlcvs($rows, $market, $timeframe, $since, $userLimit);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        // 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
        //     }
        // linear swap v2
        //
        //     {
        //         "closeRp":"16820.5",
        //         "fundingRateRr":"0.0001",
        //         "highRp":"16962.1",
        //         "indexPriceRp":"16830.15651565",
        //         "lowRp":"16785",
        //         "markPriceRp":"16830.97534951",
        //         "openInterestRv":"1323.596",
        //         "openRp":"16851.7",
        //         "predFundingRateRr":"0.0001",
        //         "symbol":"BTCUSDT",
        //         "timestamp":"1672142789065593096",
        //         "turnoverRv":"124835296.0538",
        //         "volumeRq":"7406.95"
        //     }
        //
        $marketId = $this->safe_string($ticker, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer_product($ticker, 'timestamp', 0.000001);
        $last = $this->from_ep($this->safe_string_2($ticker, 'lastEp', 'closeRp'), $market);
        $quoteVolume = $this->from_er($this->safe_string_2($ticker, 'turnoverEv', 'turnoverRv'), $market);
        $baseVolume = $this->safe_string($ticker, 'volume');
        if ($baseVolume === null) {
            $baseVolume = $this->from_ev($this->safe_string_2($ticker, 'volumeEv', 'volumeRq'), $market);
        }
        $open = $this->from_ep($this->safe_string($ticker, 'openEp'), $market);
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->from_ep($this->safe_string_2($ticker, 'highEp', 'highRp'), $market),
            'low' => $this->from_ep($this->safe_string_2($ticker, 'lowEp', 'lowRp'), $market),
            'bid' => $this->from_ep($this->safe_string($ticker, 'bidEp'), $market),
            'bidVolume' => null,
            'ask' => $this->from_ep($this->safe_string($ticker, 'askEp'), $market),
            'askVolume' => null,
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null, // previous day close
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        ), $market);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query24hrsticker
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'id' => 123456789, // optional $request id
        );
        $response = null;
        if ($market['swap']) {
            if ($market['inverse'] || $market['settle'] === 'USD') {
                $response = $this->v1GetMdTicker24hr ($this->extend($request, $params));
            } else {
                $response = $this->v2GetMdV2Ticker24hr ($this->extend($request, $params));
            }
        } else {
            $response = $this->v1GetMdSpotTicker24hr ($this->extend($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_dict($response, 'result', array());
        return $this->parse_ticker($result, $market);
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
         *
         * @see https://phemex-docs.github.io/#$query-24-hours-ticker-for-all-$symbols-2     // spot
         * @see https://phemex-docs.github.io/#$query-24-ticker-for-all-$symbols             // linear
         * @see https://phemex-docs.github.io/#$query-24-hours-ticker-for-all-$symbols       // inverse
         *
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the ticker for, all $market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $this->load_markets();
        $market = null;
        if ($symbols !== null) {
            $first = $this->safe_value($symbols, 0);
            $market = $this->market($first);
        }
        $type = null;
        list($type, $params) = $this->handle_market_type_and_params('fetchTickers', $market, $params);
        $subType = null;
        list($subType, $params) = $this->handle_sub_type_and_params('fetchTickers', $market, $params);
        $query = $this->omit($params, 'type');
        $response = null;
        if ($type === 'spot') {
            $response = $this->v1GetMdSpotTicker24hrAll ($query);
        } elseif ($subType === 'inverse' || $this->safe_string($market, 'settle') === 'USD') {
            $response = $this->v1GetMdTicker24hrAll ($query);
        } else {
            $response = $this->v2GetMdV2Ticker24hrAll ($query);
        }
        $result = $this->safe_list($response, 'result', array());
        return $this->parse_tickers($result, $symbols);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get the list of most recent $trades for a particular $symbol
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#querytrades
         *
         * @param {string} $symbol unified $symbol of the $market to fetch $trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of $trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'id' => 123456789, // optional $request id
        );
        $response = null;
        $isStableSettled = ($market['settle'] === 'USDT') || ($market['settle'] === 'USDC');
        if ($market['linear'] && $isStableSettled) {
            $response = $this->v2GetMdV2Trade ($this->extend($request, $params));
        } else {
            $response = $this->v1GetMdTrade ($this->extend($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_2($result, 'trades', 'trades_p', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public) spot & contract
        //
        //     array(
        //         1592541746712239749,
        //         13156448570000,
        //         "Buy",
        //         93070000,
        //         40173
        //     )
        //
        // fetchTrades (public) perp
        //
        //     array(
        //         1675690986063435800,
        //         "Sell",
        //         "22857.4",
        //         "0.269"
        //     )
        //
        // 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
        //         "baseCurrency" => "BTC",
        //         "quoteCurrency" => "USDT",
        //         "feeCurrency" => "BTC"
        //     }
        //
        // 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"
        //     }
        // perpetual
        //     {
        //         "accountID" => 9328670003,
        //         "action" => "New",
        //         "actionBy" => "ByUser",
        //         "actionTimeNs" => 1666858780876924611,
        //         "addedSeq" => 77751555,
        //         "apRp" => "0",
        //         "bonusChangedAmountRv" => "0",
        //         "bpRp" => "0",
        //         "clOrdID" => "c0327a7d-9064-62a9-28f6-2db9aaaa04e0",
        //         "closedPnlRv" => "0",
        //         "closedSize" => "0",
        //         "code" => 0,
        //         "cumFeeRv" => "0",
        //         "cumQty" => "0",
        //         "cumValueRv" => "0",
        //         "curAccBalanceRv" => "1508.489893982237",
        //         "curAssignedPosBalanceRv" => "24.62786650928",
        //         "curBonusBalanceRv" => "0",
        //         "curLeverageRr" => "-10",
        //         "curPosSide" => "Buy",
        //         "curPosSize" => "0.043",
        //         "curPosTerm" => 1,
        //         "curPosValueRv" => "894.0689",
        //         "curRiskLimitRv" => "1000000",
        //         "currency" => "USDT",
        //         "cxlRejReason" => 0,
        //         "displayQty" => "0.003",
        //         "execFeeRv" => "0",
        //         "execID" => "00000000-0000-0000-0000-000000000000",
        //         "execPriceRp" => "20723.7",
        //         "execQty" => "0",
        //         "execSeq" => 77751555,
        //         "execStatus" => "New",
        //         "execValueRv" => "0",
        //         "feeRateRr" => "0",
        //         "leavesQty" => "0.003",
        //         "leavesValueRv" => "63.4503",
        //         "message" => "No error",
        //         "ordStatus" => "New",
        //         "ordType" => "Market",
        //         "orderID" => "fa64c6f2-47a4-4929-aab4-b7fa9bbc4323",
        //         "orderQty" => "0.003",
        //         "pegOffsetValueRp" => "0",
        //         "posSide" => "Long",
        //         "priceRp" => "21150.1",
        //         "relatedPosTerm" => 1,
        //         "relatedReqNum" => 11,
        //         "side" => "Buy",
        //         "slTrigger" => "ByMarkPrice",
        //         "stopLossRp" => "0",
        //         "stopPxRp" => "0",
        //         "symbol" => "BTCUSDT",
        //         "takeProfitRp" => "0",
        //         "timeInForce" => "ImmediateOrCancel",
        //         "tpTrigger" => "ByLastPrice",
        //         "tradeType" => "Amend",
        //         "transactTimeNs" => 1666858780881545305,
        //         "userID" => 932867
        //     }
        //
        // swap - USDT
        //
        //     {
        //         "createdAt" => 1666226932259,
        //         "symbol" => "ETHUSDT",
        //         "currency" => "USDT",
        //         "action" => 1,
        //         "tradeType" => 1,
        //         "execQtyRq" => "0.01",
        //         "execPriceRp" => "1271.9",
        //         "side" => 1,
        //         "orderQtyRq" => "0.78",
        //         "priceRp" => "1271.9",
        //         "execValueRv" => "12.719",
        //         "feeRateRr" => "0.0001",
        //         "execFeeRv" => "0.0012719",
        //         "ordType" => 2,
        //         "execId" => "8718cae",
        //         "execStatus" => 6
        //     }
        // spot with fees paid using PT token
        //     "createdAt" => "1714990724076",
        //     "symbol" => "BTCUSDT",
        //     "currency" => "USDT",
        //     "action" => "1",
        //     "tradeType" => "1",
        //     "execQtyRq" => "0.003",
        //     "execPriceRp" => "64935",
        //     "side" => "2",
        //     "orderQtyRq" => "0.003",
        //     "priceRp" => "51600",
        //     "execValueRv" => "194.805",
        //     "feeRateRr" => "0.000495",
        //     "execFeeRv" => "0",
        //     "ordType" => "3",
        //     "execId" => "XXXXXX",
        //     "execStatus" => "7",
        //     "posSide" => "1",
        //     "ptFeeRv" => "0.110012249248",
        //     "ptPriceRp" => "0.876524893"
        //
        $id = null;
        $side = null;
        $costString = null;
        $type = null;
        $fee = null;
        $feeCostString = null;
        $feeRateString = null;
        $feeCurrencyCode = null;
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $orderId = null;
        $takerOrMaker = null;
        if (gettype($trade) === 'array' && array_keys($trade) === array_keys(array_keys($trade))) {
            $tradeLength = count($trade);
            $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);
            $priceString = $this->safe_string($trade, $tradeLength - 2);
            $amountString = $this->safe_string($trade, $tradeLength - 1);
            if ((is_float($trade[$tradeLength - 2]) || is_int($trade[$tradeLength - 2]))) {
                $priceString = $this->from_ep($priceString, $market);
                $amountString = $this->from_ev($amountString, $market);
            }
        } else {
            $timestamp = $this->safe_integer_product($trade, 'transactTimeNs', 0.000001);
            if ($timestamp === null) {
                $timestamp = $this->safe_integer($trade, 'createdAt');
            }
            $id = $this->safe_string_2($trade, 'execId', 'execID');
            $orderId = $this->safe_string($trade, 'orderID');
            if ($market['settle'] === 'USDT' || $market['settle'] === 'USDC') {
                $sideId = $this->safe_string_lower($trade, 'side');
                if (($sideId === 'buy') || ($sideId === 'sell')) {
                    $side = $sideId;
                } elseif ($sideId !== null) {
                    $side = ($sideId === '1') ? 'buy' : 'sell';
                }
                $ordType = $this->safe_string($trade, 'ordType');
                if ($ordType === '1') {
                    $type = 'market';
                } elseif ($ordType === '2') {
                    $type = 'limit';
                }
                $priceString = $this->safe_string($trade, 'execPriceRp');
                $amountString = $this->safe_string($trade, 'execQtyRq');
                $costString = $this->safe_string($trade, 'execValueRv');
                $feeCostString = $this->omit_zero($this->safe_string($trade, 'execFeeRv'));
                $feeRateString = $this->safe_string($trade, 'feeRateRr');
                if ($feeCostString !== null) {
                    $currencyId = $this->safe_string($trade, 'currency');
                    $feeCurrencyCode = $this->safe_currency_code($currencyId);
                } else {
                    $ptFeeRv = $this->omit_zero($this->safe_string($trade, 'ptFeeRv'));
                    if ($ptFeeRv !== null) {
                        $feeCostString = $ptFeeRv;
                        $feeCurrencyCode = 'PT';
                    }
                }
            } else {
                $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';
                }
                $priceString = $this->from_ep($this->safe_string($trade, 'execPriceEp'), $market);
                $amountString = $this->from_ev($this->safe_string($trade, 'execBaseQtyEv'), $market);
                $amountString = $this->safe_string($trade, 'execQty', $amountString);
                $costString = $this->from_er($this->safe_string_2($trade, 'execQuoteQtyEv', 'execValueEv'), $market);
                $feeCostString = $this->from_er($this->omit_zero($this->safe_string($trade, 'execFeeEv')), $market);
                if ($feeCostString !== null) {
                    $feeRateString = $this->from_er($this->safe_string($trade, 'feeRateEr'), $market);
                    if ($market['spot']) {
                        $feeCurrencyCode = $this->safe_currency_code($this->safe_string($trade, 'feeCurrency'));
                    } else {
                        $info = $this->safe_value($market, 'info');
                        if ($info !== null) {
                            $settlementCurrencyId = $this->safe_string($info, 'settlementCurrency');
                            $feeCurrencyCode = $this->safe_currency_code($settlementCurrencyId);
                        }
                    }
                } else {
                    $feeCostString = $this->safe_string($trade, 'ptFeeRv');
                    if ($feeCostString !== null) {
                        $feeCurrencyCode = 'PT';
                    }
                }
            }
            $fee = array(
                'cost' => $feeCostString,
                'rate' => $feeRateString,
                'currency' => $feeCurrencyCode,
            );
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $id,
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'order' => $orderId,
            'type' => $type,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => $costString,
            'fee' => $fee,
        ), $market);
    }

    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
        //             }
        //         )
        //     }
        //
        $timestamp = null;
        $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_string($balance, 'balanceEv');
            $lockedTradingBalanceEv = $this->safe_string($balance, 'lockedTradingBalanceEv');
            $lockedWithdrawEv = $this->safe_string($balance, 'lockedWithdrawEv');
            $total = $this->from_en($balanceEv, $scale);
            $lockedTradingBalance = $this->from_en($lockedTradingBalanceEv, $scale);
            $lockedWithdraw = $this->from_en($lockedWithdrawEv, $scale);
            $used = Precise::string_add($lockedTradingBalance, $lockedWithdraw);
            $lastUpdateTimeNs = $this->safe_integer_product($balance, 'lastUpdateTimeNs', 0.000001);
            $timestamp = ($timestamp === null) ? $lastUpdateTimeNs : max ($timestamp, $lastUpdateTimeNs);
            $account['total'] = $total;
            $account['used'] = $used;
            $result[$code] = $account;
        }
        $result['timestamp'] = $timestamp;
        $result['datetime'] = $this->iso8601($timestamp);
        return $this->safe_balance($result);
    }

    public function parse_swap_balance($response) {
        // usdt
        //   {
        //       "info" => {
        //         "code" => "0",
        //         "msg" => '',
        //         "data" => {
        //           "account" => array(
        //             "userID" => "940666",
        //             "accountId" => "9406660003",
        //             "currency" => "USDT",
        //             "accountBalanceRv" => "99.93143972",
        //             "totalUsedBalanceRv" => "0.40456",
        //             "bonusBalanceRv" => "0"
        //           ),
        //   }
        //
        //     {
        //         "code":0,
        //         "msg":"",
        //         "data":{
        //             "account":{
        //                 "accountId":6192120001,
        //                 "currency":"BTC",
        //                 "accountBalanceEv":1254744,
        //                 "totalUsedBalanceEv":0,
        //                 "bonusBalanceEv":1254744
        //             }
        //         }
        //     }
        //
        $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);
        $valueScale = $this->safe_integer($currency, 'valueScale', 8);
        $account = $this->account();
        $accountBalanceEv = $this->safe_string_2($balance, 'accountBalanceEv', 'accountBalanceRv');
        $totalUsedBalanceEv = $this->safe_string_2($balance, 'totalUsedBalanceEv', 'totalUsedBalanceRv');
        $needsConversion = ($code !== 'USDT');
        $account['total'] = $needsConversion ? $this->from_en($accountBalanceEv, $valueScale) : $accountBalanceEv;
        $account['used'] = $needsConversion ? $this->from_en($totalUsedBalanceEv, $valueScale) : $totalUsedBalanceEv;
        $result[$code] = $account;
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://phemex-docs.github.io/#query-wallets
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query-account-positions
         * @see https://phemex-docs.github.io/#query-trading-account-and-positions
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->type] spot or swap
         * @param {string} [$params->code] *swap only* $currency $code of the balance to query (USD, USDT, etc), default is USDT
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $type = null;
        list($type, $params) = $this->handle_market_type_and_params('fetchBalance', null, $params);
        $code = $this->safe_string($params, 'code');
        $params = $this->omit($params, array( 'code' ));
        $response = null;
        $request = array();
        if (($type !== 'spot') && ($type !== 'swap')) {
            throw new BadRequest($this->id . ' does not support ' . $type . ' markets, only spot and swap');
        }
        if ($type === 'swap') {
            $settle = null;
            list($settle, $params) = $this->handle_option_and_params($params, 'fetchBalance', 'settle', 'USDT');
            if ($code !== null || $settle !== null) {
                $coin = null;
                if ($code !== null) {
                    $coin = $code;
                } else {
                    $coin = $settle;
                }
                $currency = $this->currency($coin);
                $request['currency'] = $currency['id'];
                if ($currency['id'] === 'USDT') {
                    $response = $this->privateGetGAccountsAccountPositions ($this->extend($request, $params));
                } else {
                    $response = $this->privateGetAccountsAccountPositions ($this->extend($request, $params));
                }
            } else {
                $currency = $this->safe_string($params, 'currency');
                if ($currency === null) {
                    throw new ArgumentsRequired($this->id . ' fetchBalance() requires a $code parameter or a $currency or $settle parameter for ' . $type . ' type');
                }
                $response = $this->privateGetSpotWallets ($this->extend($request, $params));
            }
        } else {
            $response = $this->privateGetSpotWallets ($this->extend($request, $params));
        }
        //
        // usdt
        //   {
        //       "info" => {
        //         "code" => "0",
        //         "msg" => '',
        //         "data" => {
        //           "account" => array(
        //             "userID" => "940666",
        //             "accountId" => "9406660003",
        //             "currency" => "USDT",
        //             "accountBalanceRv" => "99.93143972",
        //             "totalUsedBalanceRv" => "0.40456",
        //             "bonusBalanceRv" => "0"
        //           ),
        //   }
        //
        // 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
        //                 }
        //             )
        //         }
        //     }
        //
        if ($type === 'swap') {
            return $this->parse_swap_balance($response);
        }
        return $this->parse_spot_balance($response);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'Created' => 'open',
            'Untriggered' => 'open',
            'Deactivated' => 'closed',
            'Triggered' => 'open',
            'Rejected' => 'rejected',
            'New' => 'open',
            'PartiallyFilled' => 'open',
            'Filled' => 'closed',
            'Canceled' => 'canceled',
            'Suspended' => 'canceled',
            '1' => 'open',
            '2' => 'canceled',
            '3' => 'closed',
            '4' => 'canceled',
            '5' => 'open',
            '6' => 'open',
            '7' => 'closed',
            '8' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type(?string $type) {
        $types = array(
            '1' => 'market',
            '2' => 'limit',
            '3' => 'stop',
            '4' => 'stopLimit',
            '5' => 'market',
            '6' => 'limit',
            '7' => 'market',
            '8' => 'market',
            '9' => 'stopLimit',
            '10' => 'market',
            'Limit' => 'limit',
            'Market' => 'market',
        );
        return $this->safe_string($types, $type, $type);
    }

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

    public function parse_spot_order(array $order, ?array $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');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $price = $this->from_ep($this->safe_string($order, 'priceEp'), $market);
        $amount = $this->from_ev($this->safe_string($order, 'baseQtyEv'), $market);
        $remaining = $this->omit_zero($this->from_ev($this->safe_string($order, 'leavesBaseQtyEv'), $market));
        $filled = $this->from_ev($this->safe_string_2($order, 'cumBaseQtyEv', 'cumBaseValueEv'), $market);
        $cost = $this->from_er($this->safe_string_2($order, 'cumQuoteValueEv', 'quoteQtyEv'), $market);
        $average = $this->from_ep($this->safe_string($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_string($order, 'cumFeeEv'), $market);
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $this->safe_currency_code($this->safe_string($order, 'feeCurrency')),
            );
        }
        $timeInForce = $this->parse_time_in_force($this->safe_string($order, 'timeInForce'));
        $triggerPrice = $this->parse_number($this->omit_zero($this->from_ep($this->safe_string($order, 'stopPxEp'))));
        $postOnly = ($timeInForce === 'PO');
        return $this->safe_order(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,
            'triggerPrice' => $triggerPrice,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        ), $market);
    }

    public function parse_order_side($side) {
        $sides = array(
            '1' => 'buy',
            '2' => 'sell',
        );
        return $this->safe_string($sides, $side, $side);
    }

    public function parse_swap_order($order, ?array $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",
        //         "execInst" => "ReduceOnly"
        //     }
        //
        // usdt
        // {
        //        "bizError":"0",
        //        "orderID":"bd720dff-5647-4596-aa4e-656bac87aaad",
        //        "clOrdID":"ccxt2022843dffac9477b497",
        //        "symbol":"LTCUSDT",
        //        "side":"Buy",
        //        "actionTimeNs":"1677667878751724052",
        //        "transactTimeNs":"1677667878754017434",
        //        "orderType":"Limit",
        //        "priceRp":"40",
        //        "orderQtyRq":"0.1",
        //        "displayQtyRq":"0.1",
        //        "timeInForce":"GoodTillCancel",
        //        "reduceOnly":false,
        //        "closedPnlRv":"0",
        //        "closedSizeRq":"0",
        //        "cumQtyRq":"0",
        //        "cumValueRv":"0",
        //        "leavesQtyRq":"0.1",
        //        "leavesValueRv":"4",
        //        "stopDirection":"UNSPECIFIED",
        //        "stopPxRp":"0",
        //        "trigger":"UNSPECIFIED",
        //        "pegOffsetValueRp":"0",
        //        "pegOffsetProportionRr":"0",
        //        "execStatus":"New",
        //        "pegPriceType":"UNSPECIFIED",
        //        "ordStatus":"New",
        //        "execInst":"None",
        //        "takeProfitRp":"0",
        //        "stopLossRp":"0"
        //     }
        //
        // v2 orderList
        //    {
        //        "createdAt":"1677686231301",
        //        "symbol":"LTCUSDT",
        //        "orderQtyRq":"0.2",
        //        "side":"1",
        //        "posSide":"3",
        //        "priceRp":"50",
        //        "execQtyRq":"0",
        //        "leavesQtyRq":"0.2",
        //        "execPriceRp":"0",
        //        "orderValueRv":"10",
        //        "leavesValueRv":"10",
        //        "cumValueRv":"0",
        //        "stopDirection":"0",
        //        "stopPxRp":"0",
        //        "trigger":"0",
        //        "actionBy":"1",
        //        "execFeeRv":"0",
        //        "ordType":"2",
        //        "ordStatus":"5",
        //        "clOrdId":"4b3b188",
        //        "orderId":"4b3b1884-87cf-4897-b596-6693b7ed84d1",
        //        "execStatus":"5",
        //        "bizError":"0",
        //        "totalPnlRv":null,
        //        "avgTransactPriceRp":null,
        //        "orderDetailsVos":null,
        //        "tradeType":"0"
        //    }
        //
        $id = $this->safe_string_2($order, 'orderID', 'orderId');
        $clientOrderId = $this->safe_string_2($order, 'clOrdID', 'clOrdId');
        if (($clientOrderId !== null) && (strlen($clientOrderId) < 1)) {
            $clientOrderId = null;
        }
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $market = $this->safe_market($marketId, $market);
        $status = $this->parse_order_status($this->safe_string($order, 'ordStatus'));
        $side = $this->parse_order_side($this->safe_string_lower($order, 'side'));
        $type = $this->parse_order_type($this->safe_string($order, 'orderType'));
        $price = $this->safe_string($order, 'priceRp');
        if ($price === null) {
            $price = $this->from_ep($this->safe_string($order, 'priceEp'), $market);
        }
        $amount = $this->safe_number_2($order, 'orderQty', 'orderQtyRq');
        $filled = $this->safe_number_2($order, 'cumQty', 'cumQtyRq');
        $remaining = $this->safe_number_2($order, 'leavesQty', 'leavesQtyRq');
        $timestamp = $this->safe_integer_product($order, 'actionTimeNs', 0.000001);
        if ($timestamp === null) {
            $timestamp = $this->safe_integer($order, 'createdAt');
        }
        $cost = $this->safe_number_2($order, 'cumValue', 'cumValueRv');
        $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'));
        $triggerPrice = $this->omit_zero($this->safe_string_2($order, 'stopPx', 'stopPxRp'));
        $postOnly = ($timeInForce === 'PO');
        $reduceOnly = $this->safe_value($order, 'reduceOnly');
        $execInst = $this->safe_string($order, 'execInst');
        if ($execInst === 'ReduceOnly') {
            $reduceOnly = true;
        }
        $takeProfit = $this->safe_string($order, 'takeProfitRp');
        $stopLoss = $this->safe_string($order, 'stopLossRp');
        $feeValue = $this->omit_zero($this->safe_string($order, 'execFeeRv'));
        $ptFeeRv = $this->omit_zero($this->safe_string($order, 'ptFeeRv'));
        $fee = null;
        if ($feeValue !== null) {
            $fee = array(
                'cost' => $feeValue,
                'currency' => $market['quote'],
            );
        } elseif ($ptFeeRv !== null) {
            $fee = array(
                'cost' => $ptFeeRv,
                'currency' => 'PT',
            );
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'datetime' => $this->iso8601($timestamp),
            'timestamp' => $timestamp,
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'reduceOnly' => $reduceOnly,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $triggerPrice,
            'takeProfitPrice' => $takeProfit,
            'stopLossPrice' => $stopLoss,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'cost' => $cost,
            'average' => null,
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        ));
    }

    public function parse_order(array $order, ?array $market = null): array {
        $isSwap = $this->safe_bool($market, 'swap', false);
        $hasPnl = (is_array($order) && array_key_exists('closedPnl', $order)) || (is_array($order) && array_key_exists('closedPnlRv', $order)) || (is_array($order) && array_key_exists('totalPnlRv', $order));
        if ($isSwap || $hasPnl) {
            return $this->parse_swap_order($order, $market);
        }
        return $this->parse_spot_order($order, $market);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#place-order
         * @see https://phemex-docs.github.io/#place-order-http-put-prefered-3
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {float} [$params->trigger] trigger $price for conditional orders
         * @param {array} [$params->takeProfit] *swap only* *$takeProfit object in $params* containing the $triggerPrice at which the attached take profit order will be triggered (perpetual swap markets only)
         * @param {float} [$params->takeProfit.triggerPrice] take profit trigger $price
         * @param {array} [$params->stopLoss] *swap only* *$stopLoss object in $params* containing the $triggerPrice at which the attached stop loss order will be triggered (perpetual swap markets only)
         * @param {float} [$params->stopLoss.triggerPrice] stop loss trigger $price
         * @param {string} [$params->posSide] *swap only* "Merged" for one way mode, "Long" for buy $side of $hedged mode, "Short" for sell $side of $hedged mode
         * @param {bool} [$params->hedged] *swap only* true for $hedged mode, false for one way mode, default is false
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $requestSide = $this->capitalize($side);
        $type = $this->capitalize($type);
        $request = array(
            // common
            'symbol' => $market['id'],
            'side' => $requestSide, // Sell, Buy
            'ordType' => $type, // Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched (additionally for contract-markets => MarketAsLimit, StopAsLimit, MarketIfTouchedAsLimit)
            // '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',
            // 'posSide' => Position direction - "Merged" for oneway mode , "Long" / "Short" for hedge mode
        );
        $clientOrderId = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
        $stopLoss = $this->safe_value($params, 'stopLoss');
        $stopLossDefined = ($stopLoss !== null);
        $takeProfit = $this->safe_value($params, 'takeProfit');
        $takeProfitDefined = ($takeProfit !== null);
        $isStableSettled = ($market['settle'] === 'USDT') || ($market['settle'] === 'USDC');
        if ($clientOrderId === null) {
            $brokerId = $this->safe_string($this->options, 'brokerId', 'CCXT123456');
            if ($brokerId !== null) {
                $request['clOrdID'] = $brokerId . $this->uuid16();
            }
        } else {
            $request['clOrdID'] = $clientOrderId;
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId' ));
        }
        $triggerPrice = $this->safe_string_n($params, array( 'stopPx', 'stopPrice', 'triggerPrice' ));
        if ($triggerPrice !== null) {
            if ($isStableSettled) {
                $request['stopPxRp'] = $this->price_to_precision($symbol, $triggerPrice);
            } else {
                $request['stopPxEp'] = $this->to_ep($triggerPrice, $market);
            }
        }
        $params = $this->omit($params, array( 'stopPx', 'stopPrice', 'stopLoss', 'takeProfit', 'triggerPrice' ));
        if ($market['spot']) {
            $qtyType = $this->safe_value($params, 'qtyType', 'ByBase');
            if (($type === 'Market') || ($type === 'Stop') || ($type === 'MarketIfTouched')) {
                if ($price !== null) {
                    $qtyType = 'ByQuote';
                }
            }
            if ($triggerPrice !== null) {
                if ($type === 'Limit') {
                    $request['ordType'] = 'StopLimit';
                } elseif ($type === 'Market') {
                    $request['ordType'] = 'Stop';
                }
                $request['trigger'] = 'ByLastPrice';
            }
            $request['qtyType'] = $qtyType;
            if ($qtyType === 'ByQuote') {
                $cost = $this->safe_number($params, 'cost');
                $params = $this->omit($params, 'cost');
                if ($this->options['createOrderByQuoteRequiresPrice']) {
                    if ($price !== null) {
                        $amountString = $this->number_to_string($amount);
                        $priceString = $this->number_to_string($price);
                        $quoteAmount = Precise::string_mul($amountString, $priceString);
                        $cost = $this->parse_number($quoteAmount);
                    } elseif ($cost === null) {
                        throw new ArgumentsRequired($this->id . ' createOrder() ' . $qtyType . ' requires a $price argument or a $cost parameter');
                    }
                }
                $cost = ($cost === null) ? $amount : $cost;
                $costString = $this->number_to_string($cost);
                $request['quoteQtyEv'] = $this->to_ev($costString, $market);
            } else {
                $amountString = $this->number_to_string($amount);
                $request['baseQtyEv'] = $this->to_ev($amountString, $market);
            }
        } elseif ($market['swap']) {
            $hedged = $this->safe_bool($params, 'hedged', false);
            $params = $this->omit($params, 'hedged');
            $posSide = $this->safe_string_lower($params, 'posSide');
            if ($posSide === null) {
                if ($hedged) {
                    $reduceOnly = $this->safe_bool($params, 'reduceOnly');
                    if ($reduceOnly) {
                        $side = ($side === 'buy') ? 'sell' : 'buy';
                        $params = $this->omit($params, 'reduceOnly');
                    }
                    $posSide = ($side === 'buy') ? 'Long' : 'Short';
                } else {
                    $posSide = 'Merged';
                }
            }
            $posSide = $this->capitalize($posSide);
            $request['posSide'] = $posSide;
            if ($isStableSettled) {
                $request['orderQtyRq'] = $amount;
            } else {
                $request['orderQty'] = $this->parse_to_int($amount);
            }
            if ($triggerPrice !== null) {
                $triggerType = $this->safe_string($params, 'triggerType', 'ByMarkPrice');
                $request['triggerType'] = $triggerType;
                // set direction & exchange specific order $type
                $triggerDirection = null;
                list($triggerDirection, $params) = $this->handle_param_string($params, 'triggerDirection');
                if ($triggerDirection === null) {
                    throw new ArgumentsRequired($this->id . " createOrder() also requires a 'triggerDirection' parameter with either 'ascending' or 'descending' value");
                }
                // the flow defined per https://phemex-docs.github.io/#more-order-$type-examples
                if ($triggerDirection === 'ascending' || $triggerDirection === 'up') {
                    if ($side === 'sell') {
                        $request['ordType'] = ($type === 'Market') ? 'MarketIfTouched' : 'LimitIfTouched';
                    } elseif ($side === 'buy') {
                        $request['ordType'] = ($type === 'Market') ? 'Stop' : 'StopLimit';
                    }
                } elseif ($triggerDirection === 'descending' || $triggerDirection === 'down') {
                    if ($side === 'sell') {
                        $request['ordType'] = ($type === 'Market') ? 'Stop' : 'StopLimit';
                    } elseif ($side === 'buy') {
                        $request['ordType'] = ($type === 'Market') ? 'MarketIfTouched' : 'LimitIfTouched';
                    }
                }
            }
            if ($stopLossDefined || $takeProfitDefined) {
                if ($stopLossDefined) {
                    $stopLossTriggerPrice = $this->safe_value_2($stopLoss, 'triggerPrice', 'stopPrice');
                    if ($stopLossTriggerPrice === null) {
                        throw new InvalidOrder($this->id . ' createOrder() requires a trigger $price in $params["stopLoss"]["triggerPrice"] for a stop loss order');
                    }
                    if ($isStableSettled) {
                        $request['stopLossRp'] = $this->price_to_precision($symbol, $stopLossTriggerPrice);
                    } else {
                        $request['stopLossEp'] = $this->to_ep($stopLossTriggerPrice, $market);
                    }
                    $stopLossTriggerPriceType = $this->safe_string_2($stopLoss, 'triggerPriceType', 'slTrigger');
                    if ($stopLossTriggerPriceType !== null) {
                        $request['slTrigger'] = $this->safe_string($this->options['triggerPriceTypesMap'], $stopLossTriggerPriceType, $stopLossTriggerPriceType);
                    }
                    $slLimitPrice = $this->safe_string($stopLoss, 'price');
                    if ($slLimitPrice !== null) {
                        $request['slPxRp'] = $this->price_to_precision($symbol, $slLimitPrice);
                    }
                }
                if ($takeProfitDefined) {
                    $takeProfitTriggerPrice = $this->safe_value_2($takeProfit, 'triggerPrice', 'stopPrice');
                    if ($takeProfitTriggerPrice === null) {
                        throw new InvalidOrder($this->id . ' createOrder() requires a trigger $price in $params["takeProfit"]["triggerPrice"] for a take profit order');
                    }
                    if ($isStableSettled) {
                        $request['takeProfitRp'] = $this->price_to_precision($symbol, $takeProfitTriggerPrice);
                    } else {
                        $request['takeProfitEp'] = $this->to_ep($takeProfitTriggerPrice, $market);
                    }
                    $takeProfitTriggerPriceType = $this->safe_string_2($takeProfit, 'triggerPriceType', 'tpTrigger');
                    if ($takeProfitTriggerPriceType !== null) {
                        $request['tpTrigger'] = $this->safe_string($this->options['triggerPriceTypesMap'], $takeProfitTriggerPriceType, $takeProfitTriggerPriceType);
                    }
                    $tpLimitPrice = $this->safe_string($takeProfit, 'price');
                    if ($tpLimitPrice !== null) {
                        $request['tpPxRp'] = $this->price_to_precision($symbol, $tpLimitPrice);
                    }
                }
            }
        }
        if (($type === 'Limit') || ($type === 'StopLimit') || ($type === 'LimitIfTouched')) {
            if ($isStableSettled) {
                $request['priceRp'] = $this->price_to_precision($symbol, $price);
            } else {
                $priceString = $this->number_to_string($price);
                $request['priceEp'] = $this->to_ep($priceString, $market);
            }
        }
        $takeProfitPrice = $this->safe_string($params, 'takeProfitPrice');
        if ($takeProfitPrice !== null) {
            if ($isStableSettled) {
                $request['takeProfitRp'] = $this->price_to_precision($symbol, $takeProfitPrice);
            } else {
                $request['takeProfitEp'] = $this->to_ep($takeProfitPrice, $market);
            }
            $params = $this->omit($params, 'takeProfitPrice');
        }
        $stopLossPrice = $this->safe_string($params, 'stopLossPrice');
        if ($stopLossPrice !== null) {
            if ($isStableSettled) {
                $request['stopLossRp'] = $this->price_to_precision($symbol, $stopLossPrice);
            } else {
                $request['stopLossEp'] = $this->to_ep($stopLossPrice, $market);
            }
            $params = $this->omit($params, 'stopLossPrice');
        }
        $response = null;
        if ($isStableSettled) {
            $response = $this->privatePostGOrders ($this->extend($request, $params));
        } elseif ($market['contract']) {
            $response = $this->privatePostOrders ($this->extend($request, $params));
        } else {
            $response = $this->privatePostSpotOrders ($this->extend($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_dict($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        /**
         * edit a trade order
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#amend-order-by-orderid
         *
         * @param {string} $id cancel order $id
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->posSide] either 'Merged' or 'Long' or 'Short'
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $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' ));
        $isStableSettled = ($market['settle'] === 'USDT') || ($market['settle'] === 'USDC');
        if ($clientOrderId !== null) {
            $request['clOrdID'] = $clientOrderId;
        } else {
            $request['orderID'] = $id;
        }
        if ($price !== null) {
            if ($isStableSettled) {
                $request['priceRp'] = $this->price_to_precision($market['symbol'], $price);
            } else {
                $request['priceEp'] = $this->to_ep($price, $market);
            }
        }
        // Note the uppercase 'V' in 'baseQtyEV' $request-> that is exchange's requirement at this moment. However, to avoid mistakes from user $side, let's support lowercased 'baseQtyEv' too
        $finalQty = $this->safe_string($params, 'baseQtyEv');
        $params = $this->omit($params, array( 'baseQtyEv' ));
        if ($finalQty !== null) {
            $request['baseQtyEV'] = $finalQty;
        } elseif ($amount !== null) {
            if ($isStableSettled) {
                $request['orderQtyRq'] = $this->amount_to_precision($market['symbol'], $amount);
            } else {
                $request['baseQtyEV'] = $this->to_ev($amount, $market);
            }
        }
        $triggerPrice = $this->safe_string_n($params, array( 'triggerPrice', 'stopPx', 'stopPrice' ));
        if ($triggerPrice !== null) {
            if ($isStableSettled) {
                $request['stopPxRp'] = $this->price_to_precision($symbol, $triggerPrice);
            } else {
                $request['stopPxEp'] = $this->to_ep($triggerPrice, $market);
            }
        }
        $params = $this->omit($params, array( 'triggerPrice', 'stopPx', 'stopPrice' ));
        $response = null;
        if ($isStableSettled) {
            $posSide = $this->safe_string($params, 'posSide');
            if ($posSide === null) {
                $request['posSide'] = 'Merged';
            }
            $response = $this->privatePutGOrdersReplace ($this->extend($request, $params));
        } elseif ($market['swap']) {
            $response = $this->privatePutOrdersReplace ($this->extend($request, $params));
        } else {
            $response = $this->privatePutSpotOrders ($this->extend($request, $params));
        }
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#cancel-single-order-by-orderid
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->posSide] either 'Merged' or 'Long' or 'Short'
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $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 = null;
        if ($market['settle'] === 'USDT' || $market['settle'] === 'USDC') {
            $posSide = $this->safe_string($params, 'posSide');
            if ($posSide === null) {
                $request['posSide'] = 'Merged';
            }
            $response = $this->privateDeleteGOrdersCancel ($this->extend($request, $params));
        } elseif ($market['swap']) {
            $response = $this->privateDeleteOrdersCancel ($this->extend($request, $params));
        } else {
            $response = $this->privateDeleteSpotOrders ($this->extend($request, $params));
        }
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_order($data, $market);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open orders in a $market
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#cancelall
         *
         * @param {string} $symbol unified $market $symbol of the $market to cancel orders in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $trigger = $this->safe_value_2($params, 'stop', 'trigger', false);
        $params = $this->omit($params, array( 'stop', 'trigger' ));
        $request = array(
            'symbol' => $market['id'],
            // 'untriggerred' => false, // false to cancel non-conditional orders, true to cancel conditional orders
            // 'text' => 'up to 40 characters max',
        );
        if ($trigger) {
            $request['untriggerred'] = $trigger;
        }
        $response = null;
        if ($market['settle'] === 'USDT' || $market['settle'] === 'USDC') {
            $response = $this->privateDeleteGOrdersAll ($this->extend($request, $params));
            //
            //    {
            //        code => '0',
            //        msg => '',
            //        data => '1'
            //    }
            //
        } elseif ($market['swap']) {
            $response = $this->privateDeleteOrdersAll ($this->extend($request, $params));
            //
            //    {
            //        code => '0',
            //        msg => '',
            //        data => '1'
            //    }
            //
        } else {
            $response = $this->privateDeleteSpotOrdersAll ($this->extend($request, $params));
            //
            //    {
            //        code => '0',
            //        msg => '',
            //        data => {
            //            total => '1'
            //        }
            //    }
            //
        }
        return array(
            $this->safe_order(array(
                'info' => $response,
            )),
        );
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         *
         * @see https://phemex-docs.github.io/#query-orders-by-ids
         *
         * fetches information on an $order made by the user
         * @param {string} $id the $order $id
         * @param {string} $symbol unified $symbol of the $market the $order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $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 = null;
        if ($market['settle'] === 'USDT' || $market['settle'] === 'USDC') {
            $response = $this->privateGetApiDataGFuturesOrdersByOrderId ($this->extend($request, $params));
        } elseif ($market['spot']) {
            $response = $this->privateGetApiDataSpotsOrdersByOrderId ($this->extend($request, $params));
        } else {
            $response = $this->privateGetExchangeOrder ($this->extend($request, $params));
        }
        $data = $this->safe_value($response, 'data', array());
        $order = $data;
        if (gettype($data) === 'array' && array_keys($data) === array_keys(array_keys($data))) {
            $numOrders = count($data);
            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_dict($data, 0, array());
        } elseif ($market['spot']) {
            $rows = $this->safe_list($data, 'rows', array());
            $order = $this->safe_dict($rows, 0, array());
        }
        return $this->parse_order($order, $market);
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple orders made by the user
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryorder
         *
         * @param {string} $symbol unified $market $symbol of the $market orders were made in
         * @param {int} [$since] the earliest time in ms to fetch orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($since !== null) {
            $request['start'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = null;
        if ($market['settle'] === 'USDT' || $market['settle'] === 'USDC') {
            $request['currency'] = $market['settle'];
            $response = $this->privateGetExchangeOrderV2OrderList ($this->extend($request, $params));
        } elseif ($market['swap']) {
            $response = $this->privateGetExchangeOrderList ($this->extend($request, $params));
        } else {
            $response = $this->privateGetApiDataSpotsOrders ($this->extend($request, $params));
        }
        $data = $this->safe_value($response, 'data', array());
        $rows = $this->safe_list($data, 'rows', $data);
        return $this->parse_orders($rows, $market, $since, $limit);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all unfilled currently open orders
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryopenorder
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#spotListAllOpenOrder
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of open order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOpenOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = null;
        try {
            if ($market['settle'] === 'USDT' || $market['settle'] === 'USDC') {
                $response = $this->privateGetGOrdersActiveList ($this->extend($request, $params));
            } elseif ($market['swap']) {
                $response = $this->privateGetOrdersActiveList ($this->extend($request, $params));
            } else {
                $response = $this->privateGetSpotOrders ($this->extend($request, $params));
            }
        } catch (Exception $e) {
            if ($e instanceof OrderNotFound) {
                return array();
            }
            throw $e;
        }
        $data = $this->safe_value($response, 'data', array());
        if (gettype($data) === 'array' && array_keys($data) === array_keys(array_keys($data))) {
            return $this->parse_orders($data, $market, $since, $limit);
        } else {
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_orders($rows, $market, $since, $limit);
        }
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple closed orders made by the user
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryorder
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#queryorder
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedgedd-Perpetual-API.md#query-closed-orders-by-$symbol
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#spotDataOrdersByIds
         *
         * @param {string} $symbol unified $market $symbol of the $market orders were made in
         * @param {int} [$since] the earliest time in ms to fetch orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->settle] the settlement currency to fetch orders for
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $request = array(
        );
        if ($market !== null) {
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['start'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = null;
        if (($symbol === null) || ($this->safe_string($market, 'settle') === 'USDT')) {
            $request['currency'] = $this->safe_string($params, 'settle', 'USDT');
            $response = $this->privateGetExchangeOrderV2OrderList ($this->extend($request, $params));
        } elseif ($market['swap']) {
            $response = $this->privateGetExchangeOrderList ($this->extend($request, $params));
        } else {
            $response = $this->privateGetExchangeSpotOrder ($this->extend($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' && array_keys($data) === array_keys(array_keys($data))) {
            return $this->parse_orders($data, $market, $since, $limit);
        } else {
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_orders($rows, $market, $since, $limit);
        }
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all trades made by the user
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#query-user-trade
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query-user-trade
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#spotDataTradesHist
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $request = array();
        if ($limit !== null) {
            $limit = min (200, $limit);
            $request['limit'] = $limit;
        }
        $isUSDTSettled = ($symbol === null) || ($this->safe_string($market, 'settle') === 'USDT');
        if ($isUSDTSettled) {
            $request['currency'] = 'USDT';
            $request['offset'] = 0;
            if ($limit === null) {
                $request['limit'] = 200;
            }
        } else {
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['start'] = $since;
        }
        $response = null;
        if ($isUSDTSettled) {
            $response = $this->privateGetExchangeOrderV2TradingList ($this->extend($request, $params));
        } elseif ($market['swap']) {
            $response = $this->privateGetExchangeOrderTrade ($this->extend($request, $params));
        } else {
            $response = $this->privateGetExchangeSpotOrderTrades ($this->extend($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"
        //                 ),
        //             )
        //         }
        //     }
        //
        // swap - usdt
        //
        // {
        //     "code" => 0,
        //     "msg" => "OK",
        //     "data" => {
        //         "total" => 4,
        //         "rows" => array(
        //             array(
        //                 "createdAt" => 1666226932259,
        //                 "symbol" => "ETHUSDT",
        //                 "currency" => "USDT",
        //                 "action" => 1,
        //                 "tradeType" => 1,
        //                 "execQtyRq" => "0.01",
        //                 "execPriceRp" => "1271.9",
        //                 "side" => 1,
        //                 "orderQtyRq" => "0.78",
        //                 "priceRp" => "1271.9",
        //                 "execValueRv" => "12.719",
        //                 "feeRateRr" => "0.0001",
        //                 "execFeeRv" => "0.0012719",
        //                 "ordType" => 2,
        //                 "execId" => "8718cae",
        //                 "execStatus" => 6
        //             ),
        //         )
        //     }
        // }
        //
        $data = null;
        if ($isUSDTSettled) {
            $data = $this->safe_value($response, 'data', array());
        } else {
            $data = $this->safe_value($response, 'data', array());
            $data = $this->safe_value($data, 'rows', array());
        }
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit $address for a $currency associated with this account
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->network] the chain name to fetch the deposit $address e.g. ETH, TRX, EOS, SOL, etc.
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$address-structure $address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
        );
        $defaultNetworks = $this->safe_dict($this->options, 'defaultNetworks');
        $defaultNetwork = $this->safe_string_upper($defaultNetworks, $code);
        $networks = $this->safe_dict($this->options, 'networks', array());
        $network = $this->safe_string_upper_2($params, 'network', 'chainName', $defaultNetwork);
        $network = $this->safe_string($networks, $network, $network);
        if ($network === null) {
            throw new ArgumentsRequired($this->id . ' fetchDepositAddress() requires a $network parameter');
        } else {
            $request['chainName'] = $network;
            $params = $this->omit($params, 'network');
        }
        $response = $this->privateGetExchangeWalletsV2DepositAddress ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "data" => {
        //             "address" => "tb1qxel5wq5gumt",
        //             "tag" => "",
        //             "notice" => false,
        //             "accountType" => 1,
        //             "contractName" => null,
        //             "chainTokenUrl" => null,
        //             "sign" => 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(
            'info' => $response,
            'currency' => $code,
            'network' => null,
            'address' => $address,
            'tag' => $tag,
        );
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all deposits made to an account
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch deposits for
         * @param {int} [$limit] the maximum number of deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        $response = $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_list($response, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch withdrawals for
         * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        $response = $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_list($response, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'Success' => 'ok',
            'Succeed' => 'ok',
            'Rejected' => 'failed',
            'Security check failed' => 'failed',
            'SecurityCheckFailed' => 'failed',
            'Expired' => 'failed',
            'Address Risk' => 'failed',
            'Security Checking' => 'pending',
            'SecurityChecking' => 'pending',
            'Pending Review' => 'pending',
            'Pending Transfer' => 'pending',
            'AmlCsApporve' => 'pending',
            'New' => 'pending',
            'Confirmed' => 'pending',
            'Cancelled' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // withdraw
        //
        //     {
        //         "id" => "10000001",
        //         "freezeId" => null,
        //         "address" => "44exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        //         "amountRv" => "100",
        //         "chainCode" => "11",
        //         "chainName" => "TRX",
        //         "currency" => "USDT",
        //         "currencyCode" => 3,
        //         "email" => "abc@gmail.com",
        //         "expiredTime" => "0",
        //         "feeRv" => "1",
        //         "nickName" => null,
        //         "phone" => null,
        //         "rejectReason" => "",
        //         "submitedAt" => "1670000000000",
        //         "submittedAt" => "1670000000000",
        //         "txHash" => null,
        //         "userId" => "10000001",
        //         "status" => "Success"
        //
        // fetchDeposits
        //
        //     {
        //         "id" => "29200",
        //         "currency" => "USDT",
        //         "currencyCode" => "3",
        //         "chainName" => "ETH",
        //         "chainCode" => "4",
        //         "txHash" => "0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d",
        //         "address" => "0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad",
        //         "amountEv" => "3000000000",
        //         "confirmations" => "13",
        //         "type" => "Deposit",
        //         "status" => "Success",
        //         "createdAt" => "1592722565000",
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "id" => "10000001",
        //         "userId" => "10000001",
        //         "freezeId" => "10000002",
        //         "phone" => null,
        //         "email" => "abc@gmail.com",
        //         "nickName" => null,
        //         "currency" => "USDT",
        //         "currencyCode" => "3",
        //         "status" => "Succeed",
        //         "withdrawStatus" => "Succeed",
        //         "amountEv" => "8800000000",
        //         "feeEv" => "1200000000",
        //         "address" => "0x5xxxad",
        //         "txHash => "0x0xxxx5d",
        //         "submitedAt" => "1702571922000",
        //         "submittedAt" => "1702571922000",
        //         "expiredTime" => "0",
        //         "rejectReason" => null,
        //         "chainName" => "ETH",
        //         "chainCode" => "4",
        //         "proxyAddress" => null
        //     }
        //
        $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'];
        $networkId = $this->safe_string($transaction, 'chainName');
        $timestamp = $this->safe_integer_n($transaction, array( 'createdAt', 'submitedAt', 'submittedAt' ));
        $type = $this->safe_string_lower($transaction, 'type');
        $feeCost = $this->parse_number($this->from_en($this->safe_string($transaction, 'feeEv'), $currency['valueScale']));
        if ($feeCost === null) {
            $feeCost = $this->safe_number($transaction, 'feeRv');
        }
        $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->parse_number($this->from_en($this->safe_string($transaction, 'amountEv'), $currency['valueScale']));
        if ($amount === null) {
            $amount = $this->safe_number($transaction, 'amountRv');
        }
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => $this->network_id_to_code($networkId),
            'address' => $address,
            'addressTo' => $address,
            'addressFrom' => null,
            'tag' => $tag,
            'tagTo' => $tag,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'comment' => null,
            'internal' => null,
            'fee' => $fee,
        );
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open $positions
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#query-trading-account-and-$positions
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query-account-$positions
         * @see https://phemex-docs.github.io/#query-account-$positions-with-unrealized-pnl
         *
         * @param {string[]} [$symbols] list of unified $market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->code] the $currency $code to fetch $positions for, USD, BTC or USDT, USDT is the default
         * @param {string} [$params->method] *USDT contracts only* 'privateGetGAccountsAccountPositions' or 'privateGetGAccountsAccountPositions' default is 'privateGetGAccountsAccountPositions'
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=$position-structure $position structure~
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $subType = null;
        $code = $this->safe_string_2($params, 'currency', 'code', 'USDT');
        $params = $this->omit($params, array( 'currency', 'code' ));
        $settle = null;
        $market = null;
        $firstSymbol = $this->safe_string($symbols, 0);
        if ($firstSymbol !== null) {
            $market = $this->market($firstSymbol);
            $settle = $market['settle'];
            $code = $market['settle'];
        } else {
            list($settle, $params) = $this->handle_option_and_params($params, 'fetchPositions', 'settle', $code);
        }
        list($subType, $params) = $this->handle_sub_type_and_params('fetchPositions', $market, $params);
        $isUSDTSettled = $settle === 'USDT';
        if ($isUSDTSettled) {
            $code = 'USDT';
        } elseif ($settle === 'BTC') {
            $code = 'BTC';
        } elseif ($code === null) {
            $code = ($subType === 'linear') ? 'USD' : 'BTC';
        }
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
        );
        $response = null;
        if ($isUSDTSettled) {
            $method = null;
            list($method, $params) = $this->handle_option_and_params($params, 'fetchPositions', 'method', 'privateGetGAccountsAccountPositions');
            if ($method === 'privateGetGAccountsAccountPositions') {
                $response = $this->privateGetGAccountsAccountPositions ($this->extend($request, $params));
            } else {
                $response = $this->privateGetGAccountsPositions ($this->extend($request, $params));
            }
        } else {
            $response = $this->privateGetAccountsAccountPositions ($this->extend($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());
        $result = array();
        for ($i = 0; $i < count($positions); $i++) {
            $position = $positions[$i];
            $result[] = $this->parse_position($position);
        }
        return $this->filter_by_array_positions($result, 'symbol', $symbols, false);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //    {
        //        "userID" => "811370",
        //        "accountID" => "8113700002",
        //        "symbol" => "ETHUSD",
        //        "currency" => "USD",
        //        "side" => "Buy",
        //        "positionStatus" => "Normal",
        //        "crossMargin" => false,
        //        "leverageEr" => "200000000",
        //        "leverage" => "2.00000000",
        //        "initMarginReqEr" => "50000000",
        //        "initMarginReq" => "0.50000000",
        //        "maintMarginReqEr" => "1000000",
        //        "maintMarginReq" => "0.01000000",
        //        "riskLimitEv" => "5000000000",
        //        "riskLimit" => "500000.00000000",
        //        "size" => "1",
        //        "value" => "22.22370000",
        //        "valueEv" => "222237",
        //        "avgEntryPriceEp" => "44447400",
        //        "avgEntryPrice" => "4444.74000000",
        //        "posCostEv" => "111202",
        //        "posCost" => "11.12020000",
        //        "assignedPosBalanceEv" => "111202",
        //        "assignedPosBalance" => "11.12020000",
        //        "bankruptCommEv" => "84",
        //        "bankruptComm" => "0.00840000",
        //        "bankruptPriceEp" => "22224000",
        //        "bankruptPrice" => "2222.40000000",
        //        "positionMarginEv" => "111118",
        //        "positionMargin" => "11.11180000",
        //        "liquidationPriceEp" => "22669000",
        //        "liquidationPrice" => "2266.90000000",
        //        "deleveragePercentileEr" => "0",
        //        "deleveragePercentile" => "0E-8",
        //        "buyValueToCostEr" => "50112500",
        //        "buyValueToCost" => "0.50112500",
        //        "sellValueToCostEr" => "50187500",
        //        "sellValueToCost" => "0.50187500",
        //        "markPriceEp" => "31332499",
        //        "markPrice" => "3133.24990000",
        //        "markValueEv" => "0",
        //        "markValue" => null,
        //        "unRealisedPosLossEv" => "0",
        //        "unRealisedPosLoss" => null,
        //        "estimatedOrdLossEv" => "0",
        //        "estimatedOrdLoss" => "0E-8",
        //        "usedBalanceEv" => "111202",
        //        "usedBalance" => "11.12020000",
        //        "takeProfitEp" => "0",
        //        "takeProfit" => null,
        //        "stopLossEp" => "0",
        //        "stopLoss" => null,
        //        "cumClosedPnlEv" => "-1546",
        //        "cumFundingFeeEv" => "1605",
        //        "cumTransactFeeEv" => "8438",
        //        "realisedPnlEv" => "0",
        //        "realisedPnl" => null,
        //        "cumRealisedPnlEv" => "0",
        //        "cumRealisedPnl" => null,
        //        "transactTimeNs" => "1641571200001885324",
        //        "takerFeeRateEr" => "0",
        //        "makerFeeRateEr" => "0",
        //        "term" => "6",
        //        "lastTermEndTimeNs" => "1607711882505745356",
        //        "lastFundingTimeNs" => "1641571200000000000",
        //        "curTermRealisedPnlEv" => "-1567",
        //        "execSeq" => "12112761561"
        //    }
        //
        $marketId = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $collateral = $this->safe_string_2($position, 'positionMargin', 'positionMarginRv');
        $notionalString = $this->safe_string_2($position, 'value', 'valueRv');
        $maintenanceMarginPercentageString = $this->safe_string_2($position, 'maintMarginReq', 'maintMarginReqRr');
        $maintenanceMarginString = Precise::string_mul($notionalString, $maintenanceMarginPercentageString);
        $initialMarginString = $this->safe_string_2($position, 'assignedPosBalance', 'assignedPosBalanceRv');
        $initialMarginPercentageString = Precise::string_div($initialMarginString, $notionalString);
        $liquidationPrice = $this->safe_number_2($position, 'liquidationPrice', 'liquidationPriceRp');
        $markPriceString = $this->safe_string_2($position, 'markPrice', 'markPriceRp');
        $contracts = $this->safe_string_2($position, 'size', 'sizeRq');
        $contractSize = $this->safe_value($market, 'contractSize');
        $contractSizeString = $this->number_to_string($contractSize);
        $leverage = $this->parse_number(Precise::string_abs(($this->safe_string_2($position, 'leverage', 'leverageRr'))));
        $entryPriceString = $this->safe_string_2($position, 'avgEntryPrice', 'avgEntryPriceRp');
        $rawSide = $this->safe_string($position, 'side');
        $side = null;
        if ($rawSide !== null) {
            $side = ($rawSide === 'Buy') ? 'long' : 'short';
        }
        // Inverse long contract => unRealizedPnl = (posSize * $contractSize) / avgEntryPrice - (posSize * $contractSize) / markPrice
        // Inverse short contract => unRealizedPnl =  (posSize *$contractSize) / markPrice - (posSize * $contractSize) / avgEntryPrice
        // Linear long contract =>  unRealizedPnl = (posSize * $contractSize) * markPrice - (posSize * $contractSize) * avgEntryPrice
        // Linear short contract =>  unRealizedPnl = (posSize * $contractSize) * avgEntryPrice - (posSize * $contractSize) * markPrice
        $priceDiff = null;
        if ($market['linear']) {
            if ($side === 'long') {
                $priceDiff = Precise::string_sub($markPriceString, $entryPriceString);
            } else {
                $priceDiff = Precise::string_sub($entryPriceString, $markPriceString);
            }
        } else {
            // inverse
            if ($side === 'long') {
                $priceDiff = Precise::string_sub(Precise::string_div('1', $entryPriceString), Precise::string_div('1', $markPriceString));
            } else {
                $priceDiff = Precise::string_sub(Precise::string_div('1', $markPriceString), Precise::string_div('1', $entryPriceString));
            }
        }
        $unrealizedPnl = Precise::string_mul(Precise::string_mul($priceDiff, $contracts), $contractSizeString);
        // the $unrealizedPnl is only available in a specific endpoint which much higher RL limits
        $apiUnrealizedPnl = $this->safe_string($position, 'unRealisedPnlRv', $unrealizedPnl);
        $marginRatio = Precise::string_div($maintenanceMarginString, $collateral);
        $isCross = $this->safe_value($position, 'crossMargin');
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'execSeq'),
            'symbol' => $symbol,
            'contracts' => $this->parse_number($contracts),
            'contractSize' => $contractSize,
            'realizedPnl' => $this->safe_number($position, 'curTermRealisedPnlRv'),
            'unrealizedPnl' => $this->parse_number($apiUnrealizedPnl),
            'leverage' => $leverage,
            'liquidationPrice' => $liquidationPrice,
            'collateral' => $this->parse_number($collateral),
            'notional' => $this->parse_number($notionalString),
            'markPrice' => $this->parse_number($markPriceString), // markPrice lags a bit ¯\_(ツ)_/¯
            'lastPrice' => null,
            'entryPrice' => $this->parse_number($entryPriceString),
            'timestamp' => null,
            'lastUpdateTimestamp' => $this->safe_integer_product($position, 'transactTimeNs', 0.000001),
            'initialMargin' => $this->parse_number($initialMarginString),
            'initialMarginPercentage' => $this->parse_number($initialMarginPercentageString),
            'maintenanceMargin' => $this->parse_number($maintenanceMarginString),
            'maintenanceMarginPercentage' => $this->parse_number($maintenanceMarginPercentageString),
            'marginRatio' => $this->parse_number($marginRatio),
            'datetime' => null,
            'marginMode' => $isCross ? 'cross' : 'isolated',
            'side' => $side,
            'hedged' => $this->safe_string($position, 'posMode') === 'Hedged',
            'percentage' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch the history of funding payments paid and received on this account
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#futureDataFundingFeesHist
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch funding history for
         * @param {int} [$limit] the maximum number of funding history structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-history-structure funding history structure~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchFundingHistory() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'limit' => 20, // Page size default 20, max 200
            // 'offset' => 0, // Page start default 0
        );
        if ($limit !== null) {
            if ($limit > 200) {
                throw new BadRequest($this->id . ' fetchFundingHistory() $limit argument cannot exceed 200');
            }
            $request['limit'] = $limit;
        }
        $response = null;
        $isStableSettled = $market['settle'] === 'USDT' || $market['settle'] === 'USDC';
        if ($isStableSettled) {
            $response = $this->privateGetApiDataGFuturesFundingFees ($this->extend($request, $params));
        } else {
            $response = $this->privateGetApiDataFuturesFundingFees ($this->extend($request, $params));
        }
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "data" => {
        //             "rows" => array(
        //                 {
        //                     "symbol" => "BTCUSD",
        //                     "currency" => "BTC",
        //                     "execQty" => 18, // "execQty" regular, but "execQtyRq" in hedge
        //                     "side" => "Buy",
        //                     "execPriceEp" => 360086455, // "execPriceEp" regular, but "execPriceRp" in hedge
        //                     "execValueEv" => 49987, // "execValueEv" regular, but "execValueRv" in hedge
        //                     "fundingRateEr" => 10000, // "fundingRateEr" regular, but "fundingRateRr" in hedge
        //                     "feeRateEr" => 10000, // "feeRateEr" regular, but "feeRateRr" in hedge
        //                     "execFeeEv" => 5, // "execFeeEv" regular, but "execFeeRv" in hedge
        //                     "createTime" => 1651881600000
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $rows = $this->safe_value($data, 'rows', array());
        $result = array();
        for ($i = 0; $i < count($rows); $i++) {
            $entry = $rows[$i];
            $timestamp = $this->safe_integer($entry, 'createTime');
            $execFee = $this->safe_string_2($entry, 'execFeeEv', 'execFeeRv');
            $currencyCode = $this->safe_currency_code($this->safe_string($entry, 'currency'));
            $result[] = array(
                'info' => $entry,
                'symbol' => $this->safe_string($entry, 'symbol'),
                'code' => $currencyCode,
                'timestamp' => $timestamp,
                'datetime' => $this->iso8601($timestamp),
                'id' => null,
                'amount' => $this->parse_funding_fee_to_precision($execFee, $market, $currencyCode),
            );
        }
        return $result;
    }

    public function parse_funding_fee_to_precision($value, ?array $market = null, ?string $currencyCode = null) {
        if ($value === null || $currencyCode === null) {
            return $value;
        }
        // it was confirmed by phemex support, that USDT contracts use direct amounts in funding fees, while USD & INVERSE needs 'valueScale'
        $isStableSettled = $market['settle'] === 'USDT' || $market['settle'] === 'USDC';
        if (!$isStableSettled) {
            $currency = $this->safe_currency($currencyCode);
            $scale = $this->safe_string($currency['info'], 'valueScale');
            $tickPrecision = $this->parse_precision($scale);
            $value = Precise::string_mul($value, $tickPrecision);
        }
        return $value;
    }

    public function fetch_funding_rate(string $symbol, $params = array ()): array {
        /**
         * fetch the current funding rate
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        if (!$market['swap']) {
            throw new BadSymbol($this->id . ' fetchFundingRate() supports swap contracts only');
        }
        $request = array(
            'symbol' => $market['id'],
        );
        $response = array();
        if (!$market['linear']) {
            $response = $this->v1GetMdTicker24hr ($this->extend($request, $params));
        } else {
            $response = $this->v2GetMdV2Ticker24hr ($this->extend($request, $params));
        }
        //
        //     {
        //         "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_funding_rate($result, $market);
    }

    public function parse_funding_rate($contract, ?array $market = null): array {
        //
        //     {
        //         "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
        //     }
        //
        // linear swap v2
        //
        //     {
        //         "closeRp":"16820.5",
        //         "fundingRateRr":"0.0001",
        //         "highRp":"16962.1",
        //         "indexPriceRp":"16830.15651565",
        //         "lowRp":"16785",
        //         "markPriceRp":"16830.97534951",
        //         "openInterestRv":"1323.596",
        //         "openRp":"16851.7",
        //         "predFundingRateRr":"0.0001",
        //         "symbol":"BTCUSDT",
        //         "timestamp":"1672142789065593096",
        //         "turnoverRv":"124835296.0538",
        //         "volumeRq":"7406.95"
        //     }
        //
        $marketId = $this->safe_string($contract, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->safe_integer_product($contract, 'timestamp', 0.000001);
        $markEp = $this->from_ep($this->safe_string($contract, 'markEp'), $market);
        $indexEp = $this->from_ep($this->safe_string($contract, 'indexEp'), $market);
        $fundingRateEr = $this->from_er($this->safe_string($contract, 'fundingRateEr'), $market);
        $nextFundingRateEr = $this->from_er($this->safe_string($contract, 'predFundingRateEr'), $market);
        return array(
            'info' => $contract,
            'symbol' => $symbol,
            'markPrice' => $this->safe_number($contract, 'markPriceRp', $markEp),
            'indexPrice' => $this->safe_number($contract, 'indexPriceRp', $indexEp),
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'fundingRate' => $this->safe_number($contract, 'fundingRateRr', $fundingRateEr),
            'fundingTimestamp' => null,
            'fundingDatetime' => null,
            'nextFundingRate' => $this->safe_number($contract, 'predFundingRateRr', $nextFundingRateEr),
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'interval' => null,
        );
    }

    public function set_margin(string $symbol, float $amount, $params = array ()): array {
        /**
         * Either adds or reduces margin in an isolated position in order to set the margin to a specific value
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#assign-position-balance-in-isolated-marign-mode
         *
         * @param {string} $symbol unified $market $symbol of the $market to set margin in
         * @param {float} $amount the $amount to set the margin to
         * @param {array} [$params] parameters specific to the exchange API endpoint
         * @return {array} A ~@link https://docs.ccxt.com/#/?id=add-margin-structure margin structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'posBalanceEv' => $this->to_ev($amount, $market),
        );
        $response = $this->privatePostPositionsAssign ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "",
        //         "data" => "OK"
        //     }
        //
        return $this->extend($this->parse_margin_modification($response, $market), array(
            'amount' => $amount,
        ));
    }

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

    public function parse_margin_modification(array $data, ?array $market = null): array {
        //
        //     {
        //         "code" => 0,
        //         "msg" => "",
        //         "data" => "OK"
        //     }
        //
        $market = $this->safe_market(null, $market);
        $inverse = $this->safe_value($market, 'inverse');
        $codeCurrency = $inverse ? 'base' : 'quote';
        return array(
            'info' => $data,
            'symbol' => $this->safe_symbol(null, $market),
            'type' => 'set',
            'marginMode' => 'isolated',
            'amount' => null,
            'total' => null,
            'code' => $market[$codeCurrency],
            'status' => $this->parse_margin_status($this->safe_string($data, 'code')),
            'timestamp' => null,
            'datetime' => null,
        );
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        /**
         * set margin mode to 'cross' or 'isolated'
         *
         * @see https://phemex-docs.github.io/#set-$leverage
         *
         * @param {string} $marginMode 'cross' or 'isolated'
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} response from the exchange
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' setMarginMode() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        if (!$market['swap']) {
            throw new BadSymbol($this->id . ' setMarginMode() supports swap contracts only');
        }
        $marginMode = strtolower($marginMode);
        if ($marginMode !== 'isolated' && $marginMode !== 'cross') {
            throw new BadRequest($this->id . ' setMarginMode() $marginMode argument should be isolated or cross');
        }
        $request = array(
            'symbol' => $market['id'],
        );
        $isCross = $marginMode === 'cross';
        if ($this->in_array($market['settle'], array( 'USDT', 'USDC' ))) {
            $currentLeverage = $this->safe_string($params, 'leverage');
            if ($currentLeverage === null) {
                throw new ArgumentsRequired($this->id . ' setMarginMode() requires a "leverage" parameter for USDT markets');
            }
            $request['leverageRr'] = $isCross ? Precise::string_neg(Precise::string_abs($currentLeverage)) : Precise::string_abs($currentLeverage);
            return $this->privatePutGPositionsLeverage ($this->extend($request, $params));
        }
        $leverage = $this->safe_integer($params, 'leverage');
        if ($marginMode === 'cross') {
            $leverage = 0;
        }
        if ($leverage === null) {
            throw new ArgumentsRequired($this->id . ' setMarginMode() requires a $leverage parameter');
        }
        $request['leverage'] = $leverage;
        return $this->privatePutPositionsLeverage ($this->extend($request, $params));
    }

    public function set_position_mode(bool $hedged, ?string $symbol = null, $params = array ()) {
        /**
         * set $hedged to true or false for a $market
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#switch-position-mode-synchronously
         *
         * @param {bool} $hedged set to true to use dualSidePosition
         * @param {string} $symbol not used by binance setPositionMode ()
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} response from the exchange
         */
        $this->check_required_argument('setPositionMode', $symbol, 'symbol');
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['settle'] !== 'USDT') {
            throw new BadSymbol($this->id . ' setPositionMode() supports USDT settled markets only');
        }
        $request = array(
            'symbol' => $market['id'],
        );
        if ($hedged) {
            $request['targetPosMode'] = 'Hedged';
        } else {
            $request['targetPosMode'] = 'OneWay';
        }
        return $this->privatePutGPositionsSwitchPosModeSync ($this->extend($request, $params));
    }

    public function fetch_leverage_tiers(?array $symbols = null, $params = array ()): array {
        /**
         * retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
         * @param {string[]|null} $symbols list of unified $market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=leverage-tiers-structure leverage tiers structures~, indexed by $market $symbols
         */
        $this->load_markets();
        if ($symbols !== null) {
            $first = $this->safe_value($symbols, 0);
            $market = $this->market($first);
            if ($market['settle'] !== 'USD') {
                throw new BadSymbol($this->id . ' fetchLeverageTiers() supports USD settled markets only');
            }
        }
        $response = $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]),
        //             ]
        //         }
        //     }
        //
        //
        $data = $this->safe_value($response, 'data', array());
        $riskLimits = $this->safe_list($data, 'riskLimits');
        return $this->parse_leverage_tiers($riskLimits, $symbols, 'symbol');
    }

    public function parse_market_leverage_tiers($info, ?array $market = null): array {
        /**
         * @param {array} $info Exchange $market response for 1 $market
         * @param {array} $market CCXT $market
         */
        //
        //     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),
        //         )
        //     ),
        //
        $marketId = $this->safe_string($info, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $riskLimits = ($market['info']['riskLimits']);
        $tiers = array();
        $minNotional = 0;
        for ($i = 0; $i < count($riskLimits); $i++) {
            $tier = $riskLimits[$i];
            $maxNotional = $this->safe_integer($tier, 'limit');
            $tiers[] = array(
                'tier' => $this->sum($i, 1),
                'symbol' => $this->safe_symbol($marketId, $market),
                'currency' => $market['settle'],
                'minNotional' => $minNotional,
                'maxNotional' => $maxNotional,
                'maintenanceMarginRate' => $this->safe_string($tier, 'maintenanceMargin'),
                'maxLeverage' => null,
                'info' => $tier,
            );
            $minNotional = $maxNotional;
        }
        return $tiers;
    }

    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') || ($url === '/positions/assign')) {
            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') {
                $isOrderPlacement = ($path === 'g-orders') || ($path === 'spot/orders') || ($path === 'orders');
                if ($isOrderPlacement) {
                    if ($this->safe_string($params, 'clOrdID') === null) {
                        $id = $this->safe_string($this->options, 'brokerId', 'CCXT123456');
                        $params['clOrdID'] = $id . $this->uuid16();
                    }
                }
                $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), 'sha256');
        }
        $url = $this->implode_hostname($this->urls['api'][$api]) . $url;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        /**
         * set the level of $leverage for a $market
         *
         * @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#set-$leverage
         *
         * @param {float} $leverage the rate of $leverage, 100 > $leverage > -100 excluding numbers between -1 to 1
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {bool} [$params->hedged] set to true if hedged position mode is enabled (by default long and short $leverage are set to the same value)
         * @param {float} [$params->longLeverageRr] *hedged mode only* set the $leverage for long positions
         * @param {float} [$params->shortLeverageRr] *hedged mode only* set the $leverage for short positions
         * @return {array} $response from the exchange
         */
        // WARNING => THIS WILL INCREASE LIQUIDATION PRICE FOR OPEN ISOLATED LONG POSITIONS
        // AND DECREASE LIQUIDATION PRICE FOR OPEN ISOLATED SHORT POSITIONS
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
        }
        if (($leverage < -100) || ($leverage > 100)) {
            throw new BadRequest($this->id . ' setLeverage() $leverage should be between -100 and 100');
        }
        $this->load_markets();
        $isHedged = $this->safe_bool($params, 'hedged', false);
        $longLeverageRr = $this->safe_integer($params, 'longLeverageRr');
        $shortLeverageRr = $this->safe_integer($params, 'shortLeverageRr');
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = null;
        if ($market['settle'] === 'USDT' || $market['settle'] === 'USDC') {
            if (!$isHedged && $longLeverageRr === null && $shortLeverageRr === null) {
                $request['leverageRr'] = $leverage;
            } else {
                $longVar = ($longLeverageRr !== null) ? $longLeverageRr : $leverage;
                $shortVar = ($shortLeverageRr !== null) ? $shortLeverageRr : $leverage;
                $request['longLeverageRr'] = $longVar;
                $request['shortLeverageRr'] = $shortVar;
            }
            $response = $this->privatePutGPositionsLeverage ($this->extend($request, $params));
        } else {
            $request['leverage'] = $leverage;
            $response = $this->privatePutPositionsLeverage ($this->extend($request, $params));
        }
        return $response;
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): array {
        /**
         * $transfer $currency internally between wallets on the same account
         *
         * @see https://phemex-docs.github.io/#$transfer-between-spot-and-futures
         * @see https://phemex-docs.github.io/#universal-$transfer-main-account-only-$transfer-between-sub-to-main-main-to-sub-or-sub-to-sub
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount amount to $transfer
         * @param {string} $fromAccount account to $transfer from
         * @param {string} $toAccount account to $transfer to
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->bizType] for transferring between main and sub-acounts either 'SPOT' or 'PERPETUAL' default is 'SPOT'
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$transfer-structure $transfer structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $accountsByType = $this->safe_value($this->options, 'accountsByType', array());
        $fromId = $this->safe_string($accountsByType, $fromAccount, $fromAccount);
        $toId = $this->safe_string($accountsByType, $toAccount, $toAccount);
        $scaledAmmount = $this->to_ev($amount, $currency);
        $direction = null;
        $transfer = null;
        if ($fromId === 'spot' && $toId === 'future') {
            $direction = 2;
        } elseif ($fromId === 'future' && $toId === 'spot') {
            $direction = 1;
        }
        if ($direction !== null) {
            $request = array(
                'currency' => $currency['id'],
                'moveOp' => $direction,
                'amountEv' => $scaledAmmount,
            );
            $response = $this->privatePostAssetsTransfer ($this->extend($request, $params));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "OK",
            //         "data" => {
            //             "linkKey" => "8564eba4-c9ec-49d6-9b8c-2ec5001a0fb9",
            //             "userId" => "4018340",
            //             "currency" => "USD",
            //             "amountEv" => "10",
            //             "side" => "2",
            //             "status" => "10"
            //         }
            //     }
            //
            $data = $this->safe_value($response, 'data', array());
            $transfer = $this->parse_transfer($data, $currency);
        } else { // sub account $transfer
            $request = array(
                'fromUserId' => $fromId,
                'toUserId' => $toId,
                'amountEv' => $scaledAmmount,
                'currency' => $currency['id'],
                'bizType' => $this->safe_string($params, 'bizType', 'SPOT'),
            );
            $response = $this->privatePostAssetsUniversalTransfer ($this->extend($request, $params));
            //
            //     {
            //         "code" => "0",
            //         "msg" => "OK",
            //         "data" => "API-923db826-aaaa-aaaa-aaaa-4d98c3a7c9fd"
            //     }
            //
            $transfer = $this->parse_transfer($response);
        }
        $transferOptions = $this->safe_value($this->options, 'transfer', array());
        $fillResponseFromRequest = $this->safe_bool($transferOptions, 'fillResponseFromRequest', true);
        if ($fillResponseFromRequest) {
            if ($transfer['fromAccount'] === null) {
                $transfer['fromAccount'] = $fromAccount;
            }
            if ($transfer['toAccount'] === null) {
                $transfer['toAccount'] = $toAccount;
            }
            if ($transfer['amount'] === null) {
                $transfer['amount'] = $amount;
            }
            if ($transfer['currency'] === null) {
                $transfer['currency'] = $code;
            }
        }
        return $transfer;
    }

    public function fetch_transfers(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch a history of internal $transfers made on an account
         *
         * @see https://phemex-docs.github.io/#query-transfer-history
         *
         * @param {string} $code unified $currency $code of the $currency transferred
         * @param {int} [$since] the earliest time in ms to fetch $transfers for
         * @param {int} [$limit] the maximum number of  $transfers structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structures~
         */
        $this->load_markets();
        if ($code === null) {
            throw new ArgumentsRequired($this->id . ' fetchTransfers() requires a $code argument');
        }
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
        );
        if ($since !== null) {
            $request['start'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetAssetsTransfer ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "data" => {
        //             "rows" => array(
        //                 {
        //                     "linkKey" => "87c071a3-8628-4ac2-aca1-6ce0d1fad66c",
        //                     "userId" => 4148428,
        //                     "currency" => "BTC",
        //                     "amountEv" => 67932,
        //                     "side" => 2,
        //                     "status" => 10,
        //                     "createTime" => 1652832467000,
        //                     "bizType" => 10
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $transfers = $this->safe_list($data, 'rows', array());
        return $this->parse_transfers($transfers, $currency, $since, $limit);
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        // $transfer
        //
        //     {
        //         "linkKey" => "8564eba4-c9ec-49d6-9b8c-2ec5001a0fb9",
        //         "userId" => "4018340",
        //         "currency" => "USD",
        //         "amountEv" => "10",
        //         "side" => "2",
        //         "status" => "10"
        //     }
        //
        // fetchTransfers
        //
        //     {
        //         "linkKey" => "87c071a3-8628-4ac2-aca1-6ce0d1fad66c",
        //         "userId" => 4148428,
        //         "currency" => "BTC",
        //         "amountEv" => 67932,
        //         "side" => 2,
        //         "status" => 10,
        //         "createTime" => 1652832467000,
        //         "bizType" => 10
        //     }
        //
        $id = $this->safe_string($transfer, 'linkKey');
        $status = $this->safe_string($transfer, 'status');
        $amountEv = $this->safe_string($transfer, 'amountEv');
        $amountTransfered = $this->from_ev($amountEv);
        $currencyId = $this->safe_string($transfer, 'currency');
        $code = $this->safe_currency_code($currencyId, $currency);
        $side = $this->safe_integer($transfer, 'side');
        $fromId = null;
        $toId = null;
        if ($side === 1) {
            $fromId = 'swap';
            $toId = 'spot';
        } elseif ($side === 2) {
            $fromId = 'spot';
            $toId = 'swap';
        }
        $timestamp = $this->safe_integer($transfer, 'createTime');
        return array(
            'info' => $transfer,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'currency' => $code,
            'amount' => $amountTransfered,
            'fromAccount' => $fromId,
            'toAccount' => $toId,
            'status' => $this->parse_transfer_status($status),
        );
    }

    public function parse_transfer_status(?string $status): ?string {
        $statuses = array(
            '3' => 'rejected', // 'Rejected',
            '6' => 'canceled', // 'Got error and wait for recovery',
            '10' => 'ok', // 'Success',
            '11' => 'failed', // 'Failed',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetches historical funding rate prices
         *
         * @see https://phemex-docs.github.io/#query-funding-rate-history-2
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
         * @param {int} [$since] $timestamp in ms of the earliest funding rate to fetch
         * @param {int} [$limit] the maximum amount of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~ to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
         * @param {int} [$params->until] $timestamp in ms of the latest funding rate
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $isUsdtSettled = $market['settle'] === 'USDT' || $market['settle'] === 'USDC';
        if (!$market['swap']) {
            throw new BadRequest($this->id . ' fetchFundingRateHistory() supports swap contracts only');
        }
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingRateHistory', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_deterministic('fetchFundingRateHistory', $symbol, $since, $limit, '8h', $params, 100);
        }
        $customSymbol = null;
        if ($isUsdtSettled) {
            $customSymbol = '.' . $market['id'] . 'FR8H'; // phemex requires a custom $symbol for funding rate history
        } else {
            $customSymbol = '.' . $market['baseId'] . 'FR8H';
        }
        $request = array(
            'symbol' => $customSymbol,
        );
        if ($since !== null) {
            $request['start'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        list($request, $params) = $this->handle_until_option('end', $request, $params);
        $response = null;
        if ($isUsdtSettled) {
            $response = $this->v2GetApiDataPublicDataFundingRateHistory ($this->extend($request, $params));
        } else {
            $response = $this->v1GetApiDataPublicDataFundingRateHistory ($this->extend($request, $params));
        }
        //
        //    {
        //        "code":"0",
        //        "msg":"OK",
        //        "data":{
        //           "rows":array(
        //              {
        //                 "symbol":".BTCUSDTFR8H",
        //                 "fundingRate":"0.0001",
        //                 "fundingTime":"1682064000000",
        //                 "intervalSeconds":"28800"
        //              }
        //           )
        //        }
        //    }
        //
        $data = $this->safe_value($response, 'data', array());
        $rates = $this->safe_value($data, 'rows');
        $result = array();
        for ($i = 0; $i < count($rates); $i++) {
            $item = $rates[$i];
            $timestamp = $this->safe_integer($item, 'fundingTime');
            $result[] = array(
                'info' => $item,
                'symbol' => $symbol,
                'fundingRate' => $this->safe_number($item, 'fundingRate'),
                'timestamp' => $timestamp,
                'datetime' => $this->iso8601($timestamp),
            );
        }
        $sorted = $this->sort_by($result, 'timestamp');
        return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://phemex-docs.github.io/#create-withdraw-$request
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the phemex api endpoint
         * @param {string} [$params->network] unified network $code
         * @return {array} a {@link https://github.com/ccxt/ccxt/wiki/Manual#transaction-structure transaction structure}
         */
        list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
        $this->load_markets();
        $this->check_address($address);
        $currency = $this->currency($code);
        $networkCode = null;
        list($networkCode, $params) = $this->handle_network_code_and_params($params);
        $networkId = null;
        if ($networkCode !== null) {
            $networkId = $this->network_code_to_id($networkCode);
        }
        $stableCoins = $this->safe_value($this->options, 'stableCoins');
        if ($networkId === null) {
            if (!($this->in_array($code, $stableCoins))) {
                $networkId = $currency['id'];
            } else {
                throw new ArgumentsRequired($this->id . ' withdraw () requires an extra argument $params["network"]');
            }
        }
        $request = array(
            'currency' => $currency['id'],
            'address' => $address,
            'amount' => $amount,
            'chainName' => strtoupper($networkId),
        );
        if ($tag !== null) {
            $request['addressTag'] = $tag;
        }
        $response = $this->privatePostPhemexWithdrawWalletsApiCreateWithdraw ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "data" => {
        //             "id" => "10000001",
        //             "freezeId" => null,
        //             "address" => "44exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        //             "amountRv" => "100",
        //             "chainCode" => "11",
        //             "chainName" => "TRX",
        //             "currency" => "USDT",
        //             "currencyCode" => 3,
        //             "email" => "abc@gmail.com",
        //             "expiredTime" => "0",
        //             "feeRv" => "1",
        //             "nickName" => null,
        //             "phone" => null,
        //             "rejectReason" => "",
        //             "submitedAt" => "1670000000000",
        //             "submittedAt" => "1670000000000",
        //             "txHash" => null,
        //             "userId" => "10000001",
        //             "status" => "Success"
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_transaction($data, $currency);
    }

    public function fetch_open_interest(string $symbol, $params = array ()) {
        /**
         * retrieves the open interest of a trading pair
         *
         * @see https://phemex-docs.github.io/#query-24-hours-ticker
         *
         * @param {string} $symbol unified CCXT $market $symbol
         * @param {array} [$params] exchange specific parameters
         * @return {array} an open interest structurearray(@link https://docs.ccxt.com/#/?id=open-interest-structure)
         */
        $this->load_markets();
        $market = $this->market($symbol);
        if (!$market['contract']) {
            throw new BadRequest($this->id . ' fetchOpenInterest is only supported for contract markets.');
        }
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->v2GetMdV2Ticker24hr ($this->extend($request, $params));
        //
        //    {
        //        error => null,
        //        id => '0',
        //        $result => {
        //          closeRp => '67550.1',
        //          fundingRateRr => '0.0001',
        //          highRp => '68400',
        //          indexPriceRp => '67567.15389794',
        //          lowRp => '66096.4',
        //          markPriceRp => '67550.1',
        //          openInterestRv => '1848.1144186',
        //          openRp => '66330',
        //          predFundingRateRr => '0.0001',
        //          $symbol => 'BTCUSDT',
        //          timestamp => '1729114315443343001',
        //          turnoverRv => '228863389.3237532',
        //          volumeRq => '3388.5600312'
        //        }
        //    }
        //
        $result = $this->safe_dict($response, 'result');
        return $this->parse_open_interest($result, $market);
    }

    public function parse_open_interest($interest, ?array $market = null) {
        //
        //    {
        //        closeRp => '67550.1',
        //        fundingRateRr => '0.0001',
        //        highRp => '68400',
        //        indexPriceRp => '67567.15389794',
        //        lowRp => '66096.4',
        //        markPriceRp => '67550.1',
        //        openInterestRv => '1848.1144186',
        //        openRp => '66330',
        //        predFundingRateRr => '0.0001',
        //        symbol => 'BTCUSDT',
        //        $timestamp => '1729114315443343001',
        //        turnoverRv => '228863389.3237532',
        //        volumeRq => '3388.5600312'
        //    }
        //
        $timestamp = $this->safe_integer($interest, 'timestamp') / 1000000;
        $id = $this->safe_string($interest, 'symbol');
        return $this->safe_open_interest(array(
            'info' => $interest,
            'symbol' => $this->safe_symbol($id, $market),
            'baseVolume' => $this->safe_string($interest, 'volumeRq'),
            'quoteVolume' => null,  // deprecated
            'openInterestAmount' => $this->safe_string($interest, 'openInterestRv'),
            'openInterestValue' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        ), $market);
    }

    public function fetch_convert_quote(string $fromCode, string $toCode, ?float $amount = null, $params = array ()): array {
        /**
         * fetch a quote for converting from one currency to another
         *
         * @see https://phemex-docs.github.io/#rfq-quote
         *
         * @param {string} $fromCode the currency that you want to sell and convert from
         * @param {string} $toCode the currency that you want to buy and convert into
         * @param {float} $amount how much you want to trade in units of the from currency
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=conversion-structure conversion structure~
         */
        $this->load_markets();
        $fromCurrency = $this->currency($fromCode);
        $toCurrency = $this->currency($toCode);
        $valueScale = $this->safe_integer($fromCurrency, 'valueScale');
        $request = array(
            'fromCurrency' => $fromCode,
            'toCurrency' => $toCode,
            'fromAmountEv' => $this->to_en($amount, $valueScale),
        );
        $response = $this->privateGetAssetsQuote ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "data" => {
        //             "code" => "GIF...AAA",
        //             "quoteArgs" => {
        //                 "origin" => 10,
        //                 "price" => "0.00000939",
        //                 "proceeds" => "0.00000000",
        //                 "ttlMs" => 7000,
        //                 "expireAt" => 1739875826009,
        //                 "requestAt" => 1739875818009,
        //                 "quoteAt" => 1739875816594
        //             }
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_conversion($data, $fromCurrency, $toCurrency);
    }

    public function create_convert_trade(string $id, string $fromCode, string $toCode, ?float $amount = null, $params = array ()): array {
        /**
         * convert from one currency $to another
         *
         * @see https://phemex-docs.github.io/#convert
         *
         * @param {string} $id the $id of the trade that you want $to make
         * @param {string} $fromCode the currency that you want $to sell and convert from
         * @param {string} $toCode the currency that you want $to buy and convert into
         * @param {float} [$amount] how much you want $to trade in units of the from currency
         * @param {array} [$params] extra parameters specific $to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?$id=conversion-structure conversion structure~
         */
        $this->load_markets();
        $fromCurrency = $this->currency($fromCode);
        $toCurrency = $this->currency($toCode);
        $valueScale = $this->safe_integer($fromCurrency, 'valueScale');
        $request = array(
            'code' => $id,
            'fromCurrency' => $fromCode,
            'toCurrency' => $toCode,
        );
        if ($amount !== null) {
            $request['fromAmountEv'] = $this->to_en($amount, $valueScale);
        }
        $response = $this->privatePostAssetsConvert ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "data" => {
        //             "moveOp" => 0,
        //             "fromCurrency" => "USDT",
        //             "toCurrency" => "BTC",
        //             "fromAmountEv" => 4000000000,
        //             "toAmountEv" => 41511,
        //             "linkKey" => "45c8ed8e-d3f4-472d-8262-e464e8c46247",
        //             "status" => 10
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $fromCurrencyId = $this->safe_string($data, 'fromCurrency');
        $fromResult = $this->safe_currency($fromCurrencyId, $fromCurrency);
        $toCurrencyId = $this->safe_string($data, 'toCurrency');
        $to = $this->safe_currency($toCurrencyId, $toCurrency);
        return $this->parse_conversion($data, $fromResult, $to);
    }

    public function fetch_convert_trade_history(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch the users history of conversion trades
         *
         * @see https://phemex-docs.github.io/#query-convert-history
         *
         * @param {string} [$code] the unified currency $code
         * @param {int} [$since] the earliest time in ms to fetch conversions for
         * @param {int} [$limit] the maximum number of conversion structures to retrieve, default 20, max 200
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->until] the end time in ms
         * @param {string} [$params->fromCurrency] the currency that you sold and converted from
         * @param {string} [$params->toCurrency] the currency that you bought and converted into
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=conversion-structure conversion structures~
         */
        $this->load_markets();
        $request = array();
        if ($code !== null) {
            $request['fromCurrency'] = $code;
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        list($request, $params) = $this->handle_until_option('endTime', $request, $params);
        $response = $this->privateGetAssetsConvert ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "OK",
        //         "data" => {
        //             "total" => 2,
        //             "rows" => array(
        //                 array(
        //                     "linkKey" => "45c8ed8e-d3f4-472d-8262-e464e8c46247",
        //                     "createTime" => 1739882294000,
        //                     "fromCurrency" => "USDT",
        //                     "toCurrency" => "BTC",
        //                     "fromAmountEv" => 4000000000,
        //                     "toAmountEv" => 41511,
        //                     "status" => 10,
        //                     "conversionRate" => 1037,
        //                     "errorCode" => 0
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        $rows = $this->safe_list($data, 'rows', array());
        return $this->parse_conversions($rows, $code, 'fromCurrency', 'toCurrency', $since, $limit);
    }

    public function parse_conversion(array $conversion, ?array $fromCurrency = null, ?array $toCurrency = null): array {
        //
        // fetchConvertQuote
        //
        //     {
        //         "code" => "GIF...AAA",
        //         "quoteArgs" => {
        //             "origin" => 10,
        //             "price" => "0.00000939",
        //             "proceeds" => "0.00000000",
        //             "ttlMs" => 7000,
        //             "expireAt" => 1739875826009,
        //             "requestAt" => 1739875818009,
        //             "quoteAt" => 1739875816594
        //         }
        //     }
        //
        // createConvertTrade
        //
        //     {
        //         "moveOp" => 0,
        //         "fromCurrency" => "USDT",
        //         "toCurrency" => "BTC",
        //         "fromAmountEv" => 4000000000,
        //         "toAmountEv" => 41511,
        //         "linkKey" => "45c8ed8e-d3f4-472d-8262-e464e8c46247",
        //         "status" => 10
        //     }
        //
        // fetchConvertTradeHistory
        //
        //     {
        //         "linkKey" => "45c8ed8e-d3f4-472d-8262-e464e8c46247",
        //         "createTime" => 1739882294000,
        //         "fromCurrency" => "USDT",
        //         "toCurrency" => "BTC",
        //         "fromAmountEv" => 4000000000,
        //         "toAmountEv" => 41511,
        //         "status" => 10,
        //         "conversionRate" => 1037,
        //         "errorCode" => 0
        //     }
        //
        $quoteArgs = $this->safe_dict($conversion, 'quoteArgs', array());
        $requestTime = $this->safe_integer($quoteArgs, 'requestAt');
        $timestamp = $this->safe_integer($conversion, 'createTime', $requestTime);
        $fromCoin = $this->safe_string($conversion, 'fromCurrency', $this->safe_string($fromCurrency, 'code'));
        $fromCode = $this->safe_currency_code($fromCoin, $fromCurrency);
        $toCoin = $this->safe_string($conversion, 'toCurrency', $this->safe_string($toCurrency, 'code'));
        $toCode = $this->safe_currency_code($toCoin, $toCurrency);
        $fromValueScale = $this->safe_integer($fromCurrency, 'valueScale');
        $toValueScale = $this->safe_integer($toCurrency, 'valueScale');
        $fromAmount = $this->from_en($this->safe_string($conversion, 'fromAmountEv'), $fromValueScale);
        if ($fromAmount === null && $quoteArgs !== null) {
            $fromAmount = $this->from_en($this->safe_string($quoteArgs, 'origin'), $fromValueScale);
        }
        $toAmount = $this->from_en($this->safe_string($conversion, 'toAmountEv'), $toValueScale);
        if ($toAmount === null && $quoteArgs !== null) {
            $toAmount = $this->from_en($this->safe_string($quoteArgs, 'proceeds'), $toValueScale);
        }
        return array(
            'info' => $conversion,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $this->safe_string($conversion, 'code'),
            'fromCurrency' => $fromCode,
            'fromAmount' => $this->parse_number($fromAmount),
            'toCurrency' => $toCode,
            'toAmount' => $this->parse_number($toAmount),
            'price' => $this->safe_number($quoteArgs, 'price'),
            'fee' => null,
        );
    }

    public function handle_errors(int $httpCode, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null; // 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
        }
        return null;
    }
}
