# -*- coding: utf-8 -*-

# 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

from ccxt.base.exchange import Exchange
from ccxt.abstract.phemex import ImplicitAPI
import hashlib
import numbers
from ccxt.base.types import Any, Balances, Conversion, Currencies, Currency, DepositAddress, Int, LeverageTier, LeverageTiers, MarginModification, Market, Num, Order, OrderBook, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, Trade, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import DuplicateOrderId
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import CancelPending
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class phemex(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        return self.deep_extend(super(phemex, self).describe(), {
            'id': 'phemex',
            'name': 'Phemex',
            'countries': ['CN'],  # China
            'rateLimit': 120.5,
            'version': 'v1',
            'certified': False,
            'pro': True,
            'hostname': 'api.phemex.com',
            'has': {
                'CORS': None,
                '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': {
                'logo': 'https://user-images.githubusercontent.com/1294454/85225056-221eb600-b3d7-11ea-930d-564d2690e3f6.jpg',
                'test': {
                    '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': {
                    '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': {
                    'url': 'https://phemex.com/register?referralCode=EDNVJ',
                    'discount': 0.1,
                },
            },
            'timeframes': {
                '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': {
                'public': {
                    'get': {
                        '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': {
                    'get': {
                        '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': {
                    'get': {
                        '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': {
                    'get': {
                        # 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>
                        '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': {
                        # 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': {
                        # 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>
                        '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': {
                        # 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': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'taker': self.parse_number('0.001'),
                    'maker': self.parse_number('0.001'),
                },
            },
            'features': {
                'default': {
                    'sandbox': True,
                    'createOrder': {
                        'marginMode': False,
                        'triggerPrice': True,
                        # todo
                        'triggerPriceType': {
                            'mark': True,
                            'last': True,
                            'index': True,
                        },
                        'triggerDirection': False,
                        'stopLossPrice': False,  # todo
                        'takeProfitPrice': False,  # todo
                        'attachedStopLossTakeProfit': None,
                        'timeInForce': {
                            'IOC': True,
                            'FOK': True,
                            'PO': True,
                            'GTD': False,
                        },
                        'hedged': False,
                        'leverage': False,
                        'marketBuyByCost': True,
                        'marketBuyRequiresPrice': False,
                        'selfTradePrevention': False,
                        'trailing': False,
                        'iceberg': False,
                    },
                    'createOrders': None,
                    'fetchMyTrades': {
                        'marginMode': False,
                        'limit': 200,
                        'daysBack': 100000,
                        'untilDays': 2,  # todo implement
                        'symbolRequired': False,
                    },
                    'fetchOrder': {
                        'marginMode': False,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchOpenOrders': {
                        'marginMode': False,
                        'limit': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchOrders': {
                        'marginMode': False,
                        'limit': None,
                        'daysBack': None,
                        'untilDays': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchClosedOrders': {
                        'marginMode': False,
                        'limit': 200,
                        'daysBack': 100000,
                        'daysBackCanceled': 100000,
                        'untilDays': 2,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOHLCV': {
                        'limit': 1000,
                    },
                },
                'spot': {
                    'extends': 'default',
                },
                'forDerivatives': {
                    'extends': 'default',
                    'createOrder': {
                        'triggerDirection': True,
                        'attachedStopLossTakeProfit': {
                            'triggerPriceType': {
                                'mark': True,
                                'last': True,
                                'index': True,
                            },
                            'price': True,
                        },
                        'hedged': True,
                    },
                    'fetchOHLCV': {
                        'limit': 2000,
                    },
                },
                'swap': {
                    'linear': {
                        'extends': 'forDerivatives',
                    },
                    'inverse': {
                        'extends': 'forDerivatives',
                    },
                },
                'future': {
                    'linear': None,
                    'inverse': None,
                },
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
            },
            'exceptions': {
                'exact': {
                    # not documented
                    '401': AuthenticationError,  # {"code":"401","msg":"401 Failed to load API KEY."}
                    '412': BadRequest,  # {"code":412,"msg":"Missing parameter - resolution","data":null}
                    '6001': BadRequest,  # {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null}
                    # documented
                    '19999': BadRequest,  # REQUEST_IS_DUPLICATED Duplicated request ID
                    '10001': DuplicateOrderId,  # OM_DUPLICATE_ORDERID Duplicated order ID
                    '10002': OrderNotFound,  # OM_ORDER_NOT_FOUND Cannot find order ID
                    '10003': CancelPending,  # OM_ORDER_PENDING_CANCEL Cannot cancel while order is already in pending cancel status
                    '10004': CancelPending,  # OM_ORDER_PENDING_REPLACE Cannot cancel while order is already in pending cancel status
                    '10005': CancelPending,  # OM_ORDER_PENDING Cannot cancel while order is already in pending cancel status
                    '11001': InsufficientFunds,  # TE_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance
                    '11002': InvalidOrder,  # TE_INVALID_RISK_LIMIT Invalid risk limit value
                    '11003': InsufficientFunds,  # TE_NO_ENOUGH_BALANCE_FOR_NEW_RISK_LIMIT Insufficient available balance
                    '11004': InvalidOrder,  # TE_INVALID_LEVERAGE invalid input or new leverage is over maximum allowed leverage
                    '11005': InsufficientFunds,  # TE_NO_ENOUGH_BALANCE_FOR_NEW_LEVERAGE Insufficient available balance
                    '11006': ExchangeError,  # TE_CANNOT_CHANGE_POSITION_MARGIN_WITHOUT_POSITION Position size is zero. Cannot change margin
                    '11007': ExchangeError,  # TE_CANNOT_CHANGE_POSITION_MARGIN_FOR_CROSS_MARGIN Cannot change margin under CrossMargin
                    '11008': ExchangeError,  # TE_CANNOT_REMOVE_POSITION_MARGIN_MORE_THAN_ADDED exceeds the maximum removable Margin
                    '11009': ExchangeError,  # TE_CANNOT_REMOVE_POSITION_MARGIN_DUE_TO_UNREALIZED_PNL exceeds the maximum removable Margin
                    '11010': InsufficientFunds,  # TE_CANNOT_ADD_POSITION_MARGIN_DUE_TO_NO_ENOUGH_AVAILABLE_BALANCE Insufficient available balance
                    '11011': InvalidOrder,  # TE_REDUCE_ONLY_ABORT Cannot accept reduce only order
                    '11012': InvalidOrder,  # TE_REPLACE_TO_INVALID_QTY Order quantity Error
                    '11013': InvalidOrder,  # TE_CONDITIONAL_NO_POSITION Position size is zero. Cannot determine conditional order's quantity
                    '11014': InvalidOrder,  # TE_CONDITIONAL_CLOSE_POSITION_WRONG_SIDE Close position conditional order has the same side
                    '11015': InvalidOrder,  # TE_CONDITIONAL_TRIGGERED_OR_CANCELED
                    '11016': BadRequest,  # TE_ADL_NOT_TRADING_REQUESTED_ACCOUNT Request is routed to the wrong trading engine
                    '11017': ExchangeError,  # TE_ADL_CANNOT_FIND_POSITION Cannot find requested position on current account
                    '11018': ExchangeError,  # TE_NO_NEED_TO_SETTLE_FUNDING The current account does not need to pay a funding fee
                    '11019': ExchangeError,  # TE_FUNDING_ALREADY_SETTLED The current account already pays the funding fee
                    '11020': 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': ExchangeError,  # TE_INVALID_BONOUS_AMOUNT  # Grpc command cannot be negative number Invalid bonus amount
                    '11022': AccountSuspended,  # TE_REJECT_DUE_TO_BANNED Account is banned
                    '11023': ExchangeError,  # TE_REJECT_DUE_TO_IN_PROCESS_OF_LIQ Account is in the process of liquidation
                    '11024': ExchangeError,  # TE_REJECT_DUE_TO_IN_PROCESS_OF_ADL Account is in the process of auto-deleverage
                    '11025': BadRequest,  # TE_ROUTE_ERROR Request is routed to the wrong trading engine
                    '11026': ExchangeError,  # TE_UID_ACCOUNT_MISMATCH
                    '11027': BadSymbol,  # TE_SYMBOL_INVALID Invalid number ID or name
                    '11028': BadSymbol,  # TE_CURRENCY_INVALID Invalid currency ID or name
                    '11029': ExchangeError,  # TE_ACTION_INVALID Unrecognized request type
                    '11030': ExchangeError,  # TE_ACTION_BY_INVALID
                    '11031': DDoSProtection,  # TE_SO_NUM_EXCEEDS Number of total conditional orders exceeds the max limit
                    '11032': DDoSProtection,  # TE_AO_NUM_EXCEEDS Number of total active orders exceeds the max limit
                    '11033': DuplicateOrderId,  # TE_ORDER_ID_DUPLICATE Duplicated order ID
                    '11034': InvalidOrder,  # TE_SIDE_INVALID Invalid side
                    '11035': InvalidOrder,  # TE_ORD_TYPE_INVALID Invalid OrderType
                    '11036': InvalidOrder,  # TE_TIME_IN_FORCE_INVALID Invalid TimeInForce
                    '11037': InvalidOrder,  # TE_EXEC_INST_INVALID Invalid ExecType
                    '11038': InvalidOrder,  # TE_TRIGGER_INVALID Invalid trigger type
                    '11039': InvalidOrder,  # TE_STOP_DIRECTION_INVALID Invalid stop direction type
                    '11040': InvalidOrder,  # TE_NO_MARK_PRICE Cannot get valid mark price to create conditional order
                    '11041': InvalidOrder,  # TE_NO_INDEX_PRICE Cannot get valid index price to create conditional order
                    '11042': InvalidOrder,  # TE_NO_LAST_PRICE Cannot get valid last market price to create conditional order
                    '11043': InvalidOrder,  # TE_RISING_TRIGGER_DIRECTLY Conditional order would be triggered immediately
                    '11044': InvalidOrder,  # TE_FALLING_TRIGGER_DIRECTLY Conditional order would be triggered immediately
                    '11045': InvalidOrder,  # TE_TRIGGER_PRICE_TOO_LARGE Conditional order trigger price is too high
                    '11046': InvalidOrder,  # TE_TRIGGER_PRICE_TOO_SMALL Conditional order trigger price is too low
                    '11047': InvalidOrder,  # TE_BUY_TP_SHOULD_GT_BASE TakeProfile BUY conditional order trigger price needs to be greater than reference price
                    '11048': InvalidOrder,  # TE_BUY_SL_SHOULD_LT_BASE StopLoss BUY condition order price needs to be less than the reference price
                    '11049': 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': InvalidOrder,  # TE_SELL_TP_SHOULD_LT_BASE TakeProfile SELL conditional order trigger price needs to be less than reference price
                    '11051': 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': InvalidOrder,  # TE_SELL_SL_SHOULD_GT_BASE StopLoss SELL condition order price needs to be greater than the reference price
                    '11053': InvalidOrder,  # TE_PRICE_TOO_LARGE
                    '11054': InvalidOrder,  # TE_PRICE_WORSE_THAN_BANKRUPT Order price cannot be more aggressive than bankrupt price if self order has instruction to close a position
                    '11055': InvalidOrder,  # TE_PRICE_TOO_SMALL Order price is too low
                    '11056': InvalidOrder,  # TE_QTY_TOO_LARGE Order quantity is too large
                    '11057': InvalidOrder,  # TE_QTY_NOT_MATCH_REDUCE_ONLY Does not allow ReduceOnly order without position
                    '11058': InvalidOrder,  # TE_QTY_TOO_SMALL Order quantity is too small
                    '11059': InvalidOrder,  # TE_TP_SL_QTY_NOT_MATCH_POS Position size is zero. Cannot accept any TakeProfit or StopLoss order
                    '11060': InvalidOrder,  # TE_SIDE_NOT_CLOSE_POS TakeProfit or StopLoss order has wrong side. Cannot close position
                    '11061': CancelPending,  # TE_ORD_ALREADY_PENDING_CANCEL Repeated cancel request
                    '11062': InvalidOrder,  # TE_ORD_ALREADY_CANCELED Order is already canceled
                    '11063': InvalidOrder,  # TE_ORD_STATUS_CANNOT_CANCEL Order is not able to be canceled under current status
                    '11064': InvalidOrder,  # TE_ORD_ALREADY_PENDING_REPLACE Replace request is rejected because order is already in pending replace status
                    '11065': InvalidOrder,  # TE_ORD_REPLACE_NOT_MODIFIED Replace request does not modify any parameters of the order
                    '11066': InvalidOrder,  # TE_ORD_STATUS_CANNOT_REPLACE Order is not able to be replaced under current status
                    '11067': InvalidOrder,  # TE_CANNOT_REPLACE_PRICE Market conditional order cannot change price
                    '11068': 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': 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': BadSymbol,  # TE_SYMBOL_NOT_IN_RANGE The symbol is invalid
                    '11071': InvalidOrder,  # TE_ORD_STATUS_CANNOT_TRIGGER
                    '11072': InvalidOrder,  # TE_TKFR_NOT_IN_RANGE The fee value is not valid
                    '11073': InvalidOrder,  # TE_MKFR_NOT_IN_RANGE The fee value is not valid
                    '11074': InvalidOrder,  # TE_CANNOT_ATTACH_TP_SL Order request cannot contain TP/SL parameters when the account already has positions
                    '11075': InvalidOrder,  # TE_TP_TOO_LARGE TakeProfit price is too large
                    '11076': InvalidOrder,  # TE_TP_TOO_SMALL TakeProfit price is too small
                    '11077': InvalidOrder,  # TE_TP_TRIGGER_INVALID Invalid trigger type
                    '11078': InvalidOrder,  # TE_SL_TOO_LARGE StopLoss price is too large
                    '11079': InvalidOrder,  # TE_SL_TOO_SMALL StopLoss price is too small
                    '11080': InvalidOrder,  # TE_SL_TRIGGER_INVALID Invalid trigger type
                    '11081': InvalidOrder,  # TE_RISK_LIMIT_EXCEEDS Total potential position breaches current risk limit
                    '11082': InsufficientFunds,  # TE_CANNOT_COVER_ESTIMATE_ORDER_LOSS The remaining balance cannot cover the potential unrealized PnL for self new order
                    '11083': InvalidOrder,  # TE_TAKE_PROFIT_ORDER_DUPLICATED TakeProfit order already exists
                    '11084': InvalidOrder,  # TE_STOP_LOSS_ORDER_DUPLICATED StopLoss order already exists
                    '11085': DuplicateOrderId,  # TE_CL_ORD_ID_DUPLICATE ClOrdId is duplicated
                    '11086': InvalidOrder,  # TE_PEG_PRICE_TYPE_INVALID PegPriceType is invalid
                    '11087': InvalidOrder,  # TE_BUY_TS_SHOULD_LT_BASE The trailing order's StopPrice should be less than the current last price
                    '11088': InvalidOrder,  # TE_BUY_TS_SHOULD_GT_LIQ The traling order's StopPrice should be greater than the current liquidation price
                    '11089': InvalidOrder,  # TE_SELL_TS_SHOULD_LT_LIQ The traling order's StopPrice should be greater than the current last price
                    '11090': InvalidOrder,  # TE_SELL_TS_SHOULD_GT_BASE The traling order's StopPrice should be less than the current liquidation price
                    '11091': InvalidOrder,  # TE_BUY_REVERT_VALUE_SHOULD_LT_ZERO The PegOffset should be less than zero
                    '11092': InvalidOrder,  # TE_SELL_REVERT_VALUE_SHOULD_GT_ZERO The PegOffset should be greater than zero
                    '11093': InvalidOrder,  # TE_BUY_TTP_SHOULD_ACTIVATE_ABOVE_BASE The activation price should be greater than the current last price
                    '11094': InvalidOrder,  # TE_SELL_TTP_SHOULD_ACTIVATE_BELOW_BASE The activation price should be less than the current last price
                    '11095': InvalidOrder,  # TE_TRAILING_ORDER_DUPLICATED A trailing order exists already
                    '11096': InvalidOrder,  # TE_CLOSE_ORDER_CANNOT_ATTACH_TP_SL An order to close position cannot have trailing instruction
                    '11097': BadRequest,  # TE_CANNOT_FIND_WALLET_OF_THIS_CURRENCY This crypto is not supported
                    '11098': BadRequest,  # TE_WALLET_INVALID_ACTION Invalid action on wallet
                    '11099': ExchangeError,  # TE_WALLET_VID_UNMATCHED Wallet operation request has a wrong wallet vid
                    '11100': InsufficientFunds,  # TE_WALLET_INSUFFICIENT_BALANCE Wallet has insufficient balance
                    '11101': InsufficientFunds,  # TE_WALLET_INSUFFICIENT_LOCKED_BALANCE Locked balance in wallet is not enough for unlock/withdraw request
                    '11102': BadRequest,  # TE_WALLET_INVALID_DEPOSIT_AMOUNT Deposit amount must be greater than zero
                    '11103': BadRequest,  # TE_WALLET_INVALID_WITHDRAW_AMOUNT Withdraw amount must be less than zero
                    '11104': BadRequest,  # TE_WALLET_REACHED_MAX_AMOUNT Deposit makes wallet exceed max amount allowed
                    '11105': InsufficientFunds,  # TE_PLACE_ORDER_INSUFFICIENT_BASE_BALANCE Insufficient funds in base wallet
                    '11106': InsufficientFunds,  # TE_PLACE_ORDER_INSUFFICIENT_QUOTE_BALANCE Insufficient funds in quote wallet
                    '11107': ExchangeError,  # TE_CANNOT_CONNECT_TO_REQUEST_SEQ TradingEngine failed to connect with CrossEngine
                    '11108': InvalidOrder,  # TE_CANNOT_REPLACE_OR_CANCEL_MARKET_ORDER Cannot replace/amend market order
                    '11109': InvalidOrder,  # TE_CANNOT_REPLACE_OR_CANCEL_IOC_ORDER Cannot replace/amend ImmediateOrCancel order
                    '11110': InvalidOrder,  # TE_CANNOT_REPLACE_OR_CANCEL_FOK_ORDER Cannot replace/amend FillOrKill order
                    '11111': InvalidOrder,  # TE_MISSING_ORDER_ID OrderId is missing
                    '11112': InvalidOrder,  # TE_QTY_TYPE_INVALID QtyType is invalid
                    '11113': BadRequest,  # TE_USER_ID_INVALID UserId is invalid
                    '11114': InvalidOrder,  # TE_ORDER_VALUE_TOO_LARGE Order value is too large
                    '11115': InvalidOrder,  # TE_ORDER_VALUE_TOO_SMALL Order value is too small
                    '11116': InvalidOrder,  # TE_BO_NUM_EXCEEDS Details: the total count of brakcet orders should equal or less than 5
                    '11117': InvalidOrder,  # TE_BO_CANNOT_HAVE_BO_WITH_DIFF_SIDE Details: all bracket orders should have the same Side.
                    '11118': InvalidOrder,  # TE_BO_TP_PRICE_INVALID Details: bracker order take profit price is invalid
                    '11119': InvalidOrder,  # TE_BO_SL_PRICE_INVALID Details: bracker order stop loss price is invalid
                    '11120': InvalidOrder,  # TE_BO_SL_TRIGGER_PRICE_INVALID Details: bracker order stop loss trigger price is invalid
                    '11121': InvalidOrder,  # TE_BO_CANNOT_REPLACE Details: cannot replace bracket order.
                    '11122': InvalidOrder,  # TE_BO_BOTP_STATUS_INVALID Details: bracket take profit order status is invalid
                    '11123': InvalidOrder,  # TE_BO_CANNOT_PLACE_BOTP_OR_BOSL_ORDER Details: cannot place bracket take profit order
                    '11124': InvalidOrder,  # TE_BO_CANNOT_REPLACE_BOTP_OR_BOSL_ORDER Details: cannot place bracket stop loss order
                    '11125': InvalidOrder,  # TE_BO_CANNOT_CANCEL_BOTP_OR_BOSL_ORDER Details: cannot cancel bracket sl/tp order
                    '11126': InvalidOrder,  # TE_BO_DONOT_SUPPORT_API Details: doesn't support bracket order via API
                    '11128': InvalidOrder,  # TE_BO_INVALID_EXECINST Details: ExecInst value is invalid
                    '11129': InvalidOrder,  # TE_BO_MUST_BE_SAME_SIDE_AS_POS Details: bracket order should have the same side's side
                    '11130': InvalidOrder,  # TE_BO_WRONG_SL_TRIGGER_TYPE Details: bracket stop loss order trigger type is invalid
                    '11131': InvalidOrder,  # TE_BO_WRONG_TP_TRIGGER_TYPE Details: bracket take profit order trigger type is invalid
                    '11132': InvalidOrder,  # TE_BO_ABORT_BOSL_DUE_BOTP_CREATE_FAILED Details: cancel bracket stop loss order due failed to create take profit order.
                    '11133': InvalidOrder,  # TE_BO_ABORT_BOSL_DUE_BOPO_CANCELED Details: cancel bracket stop loss order due main order canceled.
                    '11134': InvalidOrder,  # TE_BO_ABORT_BOTP_DUE_BOPO_CANCELED Details: cancel bracket take profit order due main order canceled.
                    # not documented
                    '30000': BadRequest,  # {"code":30000,"msg":"Please double check input arguments","data":null}
                    '30018': BadRequest,  # {"code":30018,"msg":"phemex.data.size.uplimt","data":null}
                    '34003': PermissionDenied,  # {"code":34003,"msg":"Access forbidden","data":null}
                    '35104': InsufficientFunds,  # {"code":35104,"msg":"phemex.spot.wallet.balance.notenough","data":null}
                    '39995': RateLimitExceeded,  # {"code": "39995","msg": "Too many requests."}
                    '39996': PermissionDenied,  # {"code": "39996","msg": "Access denied."}
                    '39997': BadSymbol,  # {"code":39997,"msg":"Symbol not listed sMOVRUSDT","data":null}
                },
                'broad': {
                    '401 Insufficient privilege': PermissionDenied,  # {"code": "401","msg": "401 Insufficient privilege."}
                    '401 Request IP mismatch': PermissionDenied,  # {"code": "401","msg": "401 Request IP mismatch."}
                    'Failed to find api-key': AuthenticationError,  # {"msg":"Failed to find api-key 1c5ec63fd-660d-43ea-847a-0d3ba69e106e","code":10500}
                    'Missing required parameter': BadRequest,  # {"msg":"Missing required parameter","code":10500}
                    'API Signature verification failed': AuthenticationError,  # {"msg":"API Signature verification failed.","code":10500}
                    'Api key not found': AuthenticationError,  # {"msg":"Api key not found 698dc9e3-6faa-4910-9476-12857e79e198","code":"10500"}
                },
            },
            'options': {
                'brokerId': 'CCXT123456',  # updated from CCXT to CCXT123456
                'x-phemex-request-expiry': 60,  # in seconds
                'createOrderByQuoteRequiresPrice': True,
                'networks': {
                    'TRC20': 'TRX',
                    'ERC20': 'ETH',
                    'BEP20': 'BNB',
                },
                'defaultNetworks': {
                    'USDT': 'ETH',
                },
                'defaultSubType': 'linear',
                'accountsByType': {
                    'spot': 'spot',
                    'swap': 'future',
                },
                'stableCoins': [
                    'BUSD',
                    'FEI',
                    'TUSD',
                    'USD',
                    'USDC',
                    'USDD',
                    'USDP',
                    'USDT',
                ],
                'transfer': {
                    'fillResponseFromRequest': True,
                },
                'triggerPriceTypesMap': {
                    'last': 'ByLastPrice',
                    'mark': 'ByMarkPrice',
                    'index': 'ByIndexPrice',
                    'ask': 'ByAskPrice',
                    'bid': 'ByBidPrice',
                },
            },
        })

    def parse_safe_number(self, value=None):
        if value is None:
            return value
        parts = value.split(',')
        value = ''.join(parts)
        parts = value.split(' ')
        return self.safe_number(parts, 0)

    def parse_swap_market(self, market: dict):
        #
        #     {
        #         "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":[
        #             {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000},
        #             {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000},
        #             {"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 = self.safe_string(market, 'symbol')
        contractUnderlyingAssets = self.safe_string(market, 'contractUnderlyingAssets')
        baseId = self.safe_string(market, 'baseCurrency', contractUnderlyingAssets)
        quoteId = self.safe_string(market, 'quoteCurrency')
        settleId = self.safe_string(market, 'settleCurrency')
        base = self.safe_currency_code(baseId)
        base = base.replace(' ', '')  # replace space for junction codes, eg. `1000 SHIB`
        quote = self.safe_currency_code(quoteId)
        settle = self.safe_currency_code(settleId)
        inverse = False
        if settleId != quoteId:
            inverse = True
            # some unhandled cases
            if not ('baseCurrency' in market) and base == quote:
                base = settle
        priceScale = self.safe_integer(market, 'priceScale')
        ratioScale = self.safe_integer(market, 'ratioScale')
        valueScale = self.safe_integer(market, 'valueScale')
        minPriceEp = self.safe_string(market, 'minPriceEp')
        maxPriceEp = self.safe_string(market, 'maxPriceEp')
        makerFeeRateEr = self.safe_string(market, 'makerFeeRateEr')
        takerFeeRateEr = self.safe_string(market, 'takerFeeRateEr')
        status = self.safe_string(market, 'status')
        contractSizeString = self.safe_string(market, 'contractSize', ' ')
        contractSize: Num = None
        if settle == 'USDT':
            contractSize = self.parse_number('1')
        elif contractSizeString.find(' '):
            # "1 USD"
            # "0.005 ETH"
            parts = contractSizeString.split(' ')
            contractSize = self.parse_number(parts[0])
        else:
            # "1.0"
            contractSize = self.parse_number(contractSizeString)
        return self.safe_market_structure({
            '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': not inverse,
            'inverse': inverse,
            'taker': self.parse_number(self.from_en(takerFeeRateEr, ratioScale)),
            'maker': self.parse_number(self.from_en(makerFeeRateEr, ratioScale)),
            'contractSize': contractSize,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'priceScale': priceScale,
            'valueScale': valueScale,
            'ratioScale': ratioScale,
            'precision': {
                'amount': self.safe_number_2(market, 'lotSize', 'qtyStepSize'),
                'price': self.safe_number(market, 'tickSize'),
            },
            'limits': {
                'leverage': {
                    'min': self.parse_number('1'),
                    'max': self.safe_number(market, 'maxLeverage'),
                },
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': self.parse_number(self.from_en(minPriceEp, priceScale)),
                    'max': self.parse_number(self.from_en(maxPriceEp, priceScale)),
                },
                'cost': {
                    'min': None,
                    'max': self.parse_number(self.safe_string(market, 'maxOrderQty')),
                },
            },
            'created': None,
            'info': market,
        })

    def parse_spot_market(self, market: dict):
        #
        #     {
        #         "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 = self.safe_string_lower(market, 'type')
        id = self.safe_string(market, 'symbol')
        quoteId = self.safe_string(market, 'quoteCurrency')
        baseId = self.safe_string(market, 'baseCurrency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        status = self.safe_string(market, 'status')
        precisionAmount = self.parse_safe_number(self.safe_string(market, 'baseTickSize'))
        precisionPrice = self.parse_safe_number(self.safe_string(market, 'quoteTickSize'))
        return self.safe_market_structure({
            'id': id,
            'symbol': base + '/' + quote,
            'base': base,
            'quote': quote,
            'settle': None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': None,
            'type': type,
            'spot': True,
            'margin': False,
            'swap': False,
            'future': False,
            'option': False,
            'active': status == 'Listed',
            'contract': False,
            'linear': None,
            'inverse': None,
            'taker': self.safe_number(market, 'defaultTakerFee'),
            'maker': self.safe_number(market, 'defaultMakerFee'),
            'contractSize': None,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'priceScale': self.safe_integer(market, 'priceScale'),
            'valueScale': self.safe_integer(market, 'valueScale'),
            'ratioScale': self.safe_integer(market, 'ratioScale'),
            'precision': {
                'amount': precisionAmount,
                'price': precisionPrice,
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': precisionAmount,
                    'max': self.parse_safe_number(self.safe_string(market, 'maxBaseOrderSize')),
                },
                'price': {
                    'min': precisionPrice,
                    'max': None,
                },
                'cost': {
                    'min': self.parse_safe_number(self.safe_string(market, 'minOrderValue')),
                    'max': self.parse_safe_number(self.safe_string(market, 'maxOrderValue')),
                },
            },
            'created': self.safe_integer(market, 'listTime'),
            'info': market,
        })

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for phemex

        https://phemex-docs.github.io/#query-product-information-3

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        v2ProductsPromise = self.v2GetPublicProducts(params)
        #
        #     {
        #         "code":0,
        #         "msg":"",
        #         "data":{
        #             "currencies":[
        #                 {"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},
        #                 {"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},
        #                 {"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":[
        #                 {
        #                     "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
        #                 },
        #                 {
        #                     "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":[
        #                 {
        #                     "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":[
        #                 {
        #                     "symbol":"BTCUSD",
        #                     "steps":"50",
        #                     "riskLimits":[
        #                         {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000},
        #                         {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000},
        #                         {"limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000},
        #                     ]
        #                 },
        #             ],
        #             "leverages":[
        #                 {"initialMargin":"1.0%","initialMarginEr":1000000,"options":[1,2,3,5,10,25,50,100]},
        #                 {"initialMargin":"1.5%","initialMarginEr":1500000,"options":[1,2,3,5,10,25,50,66]},
        #                 {"initialMargin":"2.0%","initialMarginEr":2000000,"options":[1,2,3,5,10,25,33,50]},
        #             ],
        #             "riskLimitsV2":[
        #                 {
        #                     "symbol":"BTCUSDT",
        #                     "steps":"2000K",
        #                     "riskLimits":[
        #                         {"limit":2000000,"initialMarginRr":"0.01","maintenanceMarginRr":"0.005"},,
        #                         {"limit":4000000,"initialMarginRr":"0.015","maintenanceMarginRr":"0.0075"},
        #                         {"limit":6000000,"initialMarginRr":"0.02","maintenanceMarginRr":"0.01"},
        #                     ]
        #                 },
        #             ],
        #             "leveragesV2":[
        #                 {"options":[1.0,2.0,3.0,5.0,10.0,25.0,50.0,100.0],"initialMarginRr":"0.01"},
        #                 {"options":[1.0,2.0,3.0,5.0,10.0,25.0,50.0,66.67],"initialMarginRr":"0.015"},
        #                 {"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 = self.v1GetExchangePublicProducts(params)
        v2Products, v1Products = [v2ProductsPromise, v1ProductsPromise]
        v1ProductsData = self.safe_value(v1Products, 'data', [])
        #
        #     {
        #         "code":0,
        #         "msg":"OK",
        #         "data":[
        #             {
        #                 "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 = self.safe_dict(v2Products, 'data', {})
        products = self.safe_list(v2ProductsData, 'products', [])
        perpetualProductsV2 = self.safe_list(v2ProductsData, 'perpProductsV2', [])
        products = self.array_concat(products, perpetualProductsV2)
        riskLimits = self.safe_list(v2ProductsData, 'riskLimits', [])
        riskLimitsV2 = self.safe_list(v2ProductsData, 'riskLimitsV2', [])
        riskLimits = self.array_concat(riskLimits, riskLimitsV2)
        currencies = self.safe_list(v2ProductsData, 'currencies', [])
        riskLimitsById = self.index_by(riskLimits, 'symbol')
        v1ProductsById = self.index_by(v1ProductsData, 'symbol')
        currenciesByCode = self.index_by(currencies, 'currency')
        result = []
        for i in range(0, len(products)):
            market = products[i]
            type = self.safe_string_lower(market, 'type')
            if (type == 'perpetual') or (type == 'perpetualv2') or (type == 'perpetualpilot'):
                id = self.safe_string(market, 'symbol')
                riskLimitValues = self.safe_dict(riskLimitsById, id, {})
                market = self.extend(market, riskLimitValues)
                v1ProductsValues = self.safe_dict(v1ProductsById, id, {})
                market = self.extend(market, v1ProductsValues)
                market = self.parse_swap_market(market)
            else:
                baseCurrency = self.safe_string(market, 'baseCurrency')
                currencyValues = self.safe_dict(currenciesByCode, baseCurrency, {})
                valueScale = self.safe_string(currencyValues, 'valueScale', '8')
                market = self.extend(market, {'valueScale': valueScale})
                market = self.parse_spot_market(market)
            result.append(market)
        return result

    def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.v2GetPublicProducts(params)
        #
        #     {
        #         "code":0,
        #         "msg":"OK",
        #         "data":{
        #             ...,
        #             "currencies":[
        #                 {"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},
        #                 {"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},
        #                 {"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 = self.safe_value(response, 'data', {})
        currencies = self.safe_value(data, 'currencies', [])
        result: dict = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'currency')
            name = self.safe_string(currency, 'name')
            code = self.safe_currency_code(id)
            status = self.safe_string(currency, 'status')
            valueScaleString = self.safe_string(currency, 'valueScale')
            valueScale = int(valueScaleString)
            minValueEv = self.safe_string(currency, 'minValueEv')
            maxValueEv = self.safe_string(currency, 'maxValueEv')
            minAmount: Num = None
            maxAmount: Num = None
            precision: Num = None
            if valueScale is not None:
                precisionString = self.parse_precision(valueScaleString)
                precision = self.parse_number(precisionString)
                minAmount = self.parse_number(Precise.string_mul(minValueEv, precisionString))
                maxAmount = self.parse_number(Precise.string_mul(maxValueEv, precisionString))
            result[code] = {
                'id': id,
                'info': currency,
                'code': code,
                'name': name,
                'active': status == 'Listed',
                'deposit': None,
                'withdraw': None,
                'fee': None,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': minAmount,
                        'max': maxAmount,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
                'valueScale': valueScale,
                'networks': None,
                'type': 'crypto',
            }
        return result

    def custom_parse_bid_ask(self, bidask, priceKey=0, amountKey=1, market: Market = None):
        if market is None:
            raise ArgumentsRequired(self.id + ' customParseBidAsk() requires a market argument')
        amount = self.safe_string(bidask, amountKey)
        if market['spot']:
            amount = self.from_ev(amount, market)
        return [
            self.parse_number(self.from_ep(self.safe_string(bidask, priceKey), market)),
            self.parse_number(amount),
        ]

    def custom_parse_order_book(self, orderbook, symbol, timestamp=None, bidsKey='bids', asksKey='asks', priceKey=0, amountKey=1, market: Market = None):
        result: dict = {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'nonce': None,
        }
        sides = [bidsKey, asksKey]
        for i in range(0, len(sides)):
            side = sides[i]
            orders = []
            bidasks = self.safe_value(orderbook, side)
            for k in range(0, len(bidasks)):
                orders.append(self.custom_parse_bid_ask(bidasks[k], priceKey, amountKey, market))
            result[side] = orders
        result[bidsKey] = self.sort_by(result[bidsKey], 0, True)
        result[asksKey] = self.sort_by(result[asksKey], 0)
        return result

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryorderbook

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            # 'id': 123456789,  # optional request id
        }
        response = None
        if market['linear'] and market['settle'] == 'USDT':
            response = self.v2GetMdV2Orderbook(self.extend(request, params))
        else:
            if (limit is not None) and (limit <= 30):
                response = self.v1GetMdOrderbook(self.extend(request, params))
            else:
                response = self.v1GetMdFullbook(self.extend(request, params))
        #
        #     {
        #         "error": null,
        #         "id": 0,
        #         "result": {
        #             "book": {
        #                 "asks": [
        #                     [23415000000, 105262000],
        #                     [23416000000, 147914000],
        #                     [23419000000, 160914000],
        #                 ],
        #                 "bids": [
        #                     [23360000000, 32995000],
        #                     [23359000000, 221887000],
        #                     [23356000000, 284599000],
        #                 ],
        #             },
        #             "depth": 30,
        #             "sequence": 1592059928,
        #             "symbol": "sETHUSDT",
        #             "timestamp": 1592387340020000955,
        #             "type": "snapshot"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        book = self.safe_value_2(result, 'book', 'orderbook_p', {})
        timestamp = self.safe_integer_product(result, 'timestamp', 0.000001)
        orderbook = self.custom_parse_order_book(book, symbol, timestamp, 'bids', 'asks', 0, 1, market)
        orderbook['nonce'] = self.safe_integer(result, 'sequence')
        return orderbook

    def to_en(self, n, scale):
        stringN = self.number_to_string(n)
        precise = Precise(stringN)
        precise.decimals = precise.decimals - scale
        precise.reduce()
        preciseString = str(precise)
        return self.parse_to_numeric(preciseString)

    def to_ev(self, amount, market: Market = None):
        if (amount is None) or (market is None):
            return amount
        return self.to_en(amount, market['valueScale'])

    def to_ep(self, price, market: Market = None):
        if (price is None) or (market is None):
            return price
        return self.to_en(price, market['priceScale'])

    def from_en(self, en, scale):
        if en is None or scale is None:
            return None
        precise = Precise(en)
        precise.decimals = self.sum(precise.decimals, scale)
        precise.reduce()
        return str(precise)

    def from_ep(self, ep, market: Market = None):
        if (ep is None) or (market is None):
            return ep
        return self.from_en(ep, self.safe_integer(market, 'priceScale'))

    def from_ev(self, ev, market: Market = None):
        if (ev is None) or (market is None):
            return ev
        return self.from_en(ev, self.safe_integer(market, 'valueScale'))

    def from_er(self, er, market: Market = None):
        if (er is None) or (market is None):
            return er
        return self.from_en(er, self.safe_integer(market, 'ratioScale'))

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     [
        #         1592467200,  # timestamp
        #         300,  # interval
        #         23376000000,  # last
        #         23322000000,  # open
        #         23381000000,  # high
        #         23315000000,  # low
        #         23367000000,  # close
        #         208671000,  # base volume
        #         48759063370,  # quote volume
        #     ]
        #
        baseVolume: Num
        if (market is not None) and market['spot']:
            baseVolume = self.parse_number(self.from_ev(self.safe_string(ohlcv, 7), market))
        else:
            baseVolume = self.safe_number(ohlcv, 7)
        return [
            self.safe_timestamp(ohlcv, 0),
            self.parse_number(self.from_ep(self.safe_string(ohlcv, 3), market)),
            self.parse_number(self.from_ep(self.safe_string(ohlcv, 4), market)),
            self.parse_number(self.from_ep(self.safe_string(ohlcv, 5), market)),
            self.parse_number(self.from_ep(self.safe_string(ohlcv, 6), market)),
            baseVolume,
        ]

    def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#querykline
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#query-kline

        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: *USDT settled/ linear swaps only* end time in ms
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        userLimit = limit
        request: dict = {
            'symbol': market['id'],
            'resolution': self.safe_string(self.timeframes, timeframe, timeframe),
        }
        until = self.safe_integer_2(params, 'until', 'to')
        params = self.omit(params, ['until'])
        usesSpecialFromToEndpoint = ((market['linear'] or market['settle'] == 'USDT')) and ((since is not None) or (until is not None))
        maxLimit = 1000
        if usesSpecialFromToEndpoint:
            maxLimit = 2000
        if limit is None:
            limit = maxLimit
        request['limit'] = min(limit, maxLimit)
        response = None
        if market['linear'] or market['settle'] == 'USDT':
            if (until is not None) or (since is not None):
                candleDuration = self.parse_timeframe(timeframe)
                if since is not None:
                    since = int(round(since / 1000))
                    request['from'] = since
                else:
                    # when 'to' is defined since is mandatory
                    since = (until / 100) - (maxLimit * candleDuration)
                if until is not None:
                    request['to'] = int(round(until / 1000))
                else:
                    # when since is defined 'to' is mandatory
                    to = since + (maxLimit * candleDuration)
                    now = self.seconds()
                    if to > now:
                        to = now
                    request['to'] = to
                response = self.publicGetMdV2KlineList(self.extend(request, params))
            else:
                response = self.publicGetMdV2KlineLast(self.extend(request, params))
        else:
            if since is not None:
                # phemex also provides kline query with from/to, however, self 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 = self.parse_timeframe(timeframe) * 1000
                timeDelta = self.milliseconds() - since
                limit = self.parse_to_int(timeDelta / duration)  # setting limit to the number of candles after since
            response = self.publicGetMdV2Kline(self.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 = self.safe_value(response, 'data', {})
        rows = self.safe_list(data, 'rows', [])
        return self.parse_ohlcvs(rows, market, timeframe, since, userLimit)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        # 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 = self.safe_string(ticker, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        timestamp = self.safe_integer_product(ticker, 'timestamp', 0.000001)
        last = self.from_ep(self.safe_string_2(ticker, 'lastEp', 'closeRp'), market)
        quoteVolume = self.from_er(self.safe_string_2(ticker, 'turnoverEv', 'turnoverRv'), market)
        baseVolume = self.safe_string(ticker, 'volume')
        if baseVolume is None:
            baseVolume = self.from_ev(self.safe_string_2(ticker, 'volumeEv', 'volumeRq'), market)
        open = self.from_ep(self.safe_string(ticker, 'openEp'), market)
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.from_ep(self.safe_string_2(ticker, 'highEp', 'highRp'), market),
            'low': self.from_ep(self.safe_string_2(ticker, 'lowEp', 'lowRp'), market),
            'bid': self.from_ep(self.safe_string(ticker, 'bidEp'), market),
            'bidVolume': None,
            'ask': self.from_ep(self.safe_string(ticker, 'askEp'), market),
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,  # previous day close
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query24hrsticker

        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            # 'id': 123456789,  # optional request id
        }
        response = None
        if market['swap']:
            if market['inverse'] or market['settle'] == 'USD':
                response = self.v1GetMdTicker24hr(self.extend(request, params))
            else:
                response = self.v2GetMdV2Ticker24hr(self.extend(request, params))
        else:
            response = self.v1GetMdSpotTicker24hr(self.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 = self.safe_dict(response, 'result', {})
        return self.parse_ticker(result, market)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market

        https://phemex-docs.github.io/#query-24-hours-ticker-for-all-symbols-2     # spot
        https://phemex-docs.github.io/#query-24-ticker-for-all-symbols             # linear
        https://phemex-docs.github.io/#query-24-hours-ticker-for-all-symbols       # inverse

        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market: Market = None
        if symbols is not None:
            first = self.safe_value(symbols, 0)
            market = self.market(first)
        type = None
        type, params = self.handle_market_type_and_params('fetchTickers', market, params)
        subType = None
        subType, params = self.handle_sub_type_and_params('fetchTickers', market, params)
        query = self.omit(params, 'type')
        response = None
        if type == 'spot':
            response = self.v1GetMdSpotTicker24hrAll(query)
        elif subType == 'inverse' or self.safe_string(market, 'settle') == 'USD':
            response = self.v1GetMdTicker24hrAll(query)
        else:
            response = self.v2GetMdV2Ticker24hrAll(query)
        result = self.safe_list(response, 'result', [])
        return self.parse_tickers(result, symbols)

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#querytrades

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            # 'id': 123456789,  # optional request id
        }
        response = None
        if market['linear'] and market['settle'] == 'USDT':
            response = self.v2GetMdV2Trade(self.extend(request, params))
        else:
            response = self.v1GetMdTrade(self.extend(request, params))
        #
        #     {
        #         "error": null,
        #         "id": 0,
        #         "result": {
        #             "sequence": 1315644947,
        #             "symbol": "BTCUSD",
        #             "trades": [
        #                 [1592541746712239749, 13156448570000, "Buy", 93070000, 40173],
        #                 [1592541740434625085, 13156447110000, "Sell", 93065000, 5000],
        #                 [1592541732958241616, 13156441390000, "Buy", 93070000, 3460],
        #             ],
        #             "type": "snapshot"
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        trades = self.safe_value_2(result, 'trades', 'trades_p', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades(public) spot & contract
        #
        #     [
        #         1592541746712239749,
        #         13156448570000,
        #         "Buy",
        #         93070000,
        #         40173
        #     ]
        #
        # fetchTrades(public) perp
        #
        #     [
        #         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"
        #
        priceString: Str
        amountString: Str
        timestamp: Int
        id: Str = None
        side: Str = None
        costString: Str = None
        type: Str = None
        fee = None
        feeCostString: Str = None
        feeRateString: Str = None
        feeCurrencyCode: Str = None
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        orderId: Str = None
        takerOrMaker: Str = None
        if isinstance(trade, list):
            tradeLength = len(trade)
            timestamp = self.safe_integer_product(trade, 0, 0.000001)
            if tradeLength > 4:
                id = self.safe_string(trade, tradeLength - 4)
            side = self.safe_string_lower(trade, tradeLength - 3)
            priceString = self.safe_string(trade, tradeLength - 2)
            amountString = self.safe_string(trade, tradeLength - 1)
            if isinstance(trade[tradeLength - 2], numbers.Real):
                priceString = self.from_ep(priceString, market)
                amountString = self.from_ev(amountString, market)
        else:
            timestamp = self.safe_integer_product(trade, 'transactTimeNs', 0.000001)
            if timestamp is None:
                timestamp = self.safe_integer(trade, 'createdAt')
            id = self.safe_string_2(trade, 'execId', 'execID')
            orderId = self.safe_string(trade, 'orderID')
            if market['settle'] == 'USDT':
                sideId = self.safe_string_lower(trade, 'side')
                if (sideId == 'buy') or (sideId == 'sell'):
                    side = sideId
                elif sideId is not None:
                    side = 'buy' if (sideId == '1') else 'sell'
                ordType = self.safe_string(trade, 'ordType')
                if ordType == '1':
                    type = 'market'
                elif ordType == '2':
                    type = 'limit'
                priceString = self.safe_string(trade, 'execPriceRp')
                amountString = self.safe_string(trade, 'execQtyRq')
                costString = self.safe_string(trade, 'execValueRv')
                feeCostString = self.omit_zero(self.safe_string(trade, 'execFeeRv'))
                feeRateString = self.safe_string(trade, 'feeRateRr')
                if feeCostString is not None:
                    currencyId = self.safe_string(trade, 'currency')
                    feeCurrencyCode = self.safe_currency_code(currencyId)
                else:
                    ptFeeRv = self.omit_zero(self.safe_string(trade, 'ptFeeRv'))
                    if ptFeeRv is not None:
                        feeCostString = ptFeeRv
                        feeCurrencyCode = 'PT'
            else:
                side = self.safe_string_lower(trade, 'side')
                type = self.parse_order_type(self.safe_string(trade, 'ordType'))
                execStatus = self.safe_string(trade, 'execStatus')
                if execStatus == 'MakerFill':
                    takerOrMaker = 'maker'
                priceString = self.from_ep(self.safe_string(trade, 'execPriceEp'), market)
                amountString = self.from_ev(self.safe_string(trade, 'execBaseQtyEv'), market)
                amountString = self.safe_string(trade, 'execQty', amountString)
                costString = self.from_er(self.safe_string_2(trade, 'execQuoteQtyEv', 'execValueEv'), market)
                feeCostString = self.from_er(self.omit_zero(self.safe_string(trade, 'execFeeEv')), market)
                if feeCostString is not None:
                    feeRateString = self.from_er(self.safe_string(trade, 'feeRateEr'), market)
                    if market['spot']:
                        feeCurrencyCode = self.safe_currency_code(self.safe_string(trade, 'feeCurrency'))
                    else:
                        info = self.safe_value(market, 'info')
                        if info is not None:
                            settlementCurrencyId = self.safe_string(info, 'settlementCurrency')
                            feeCurrencyCode = self.safe_currency_code(settlementCurrencyId)
                else:
                    feeCostString = self.safe_string(trade, 'ptFeeRv')
                    if feeCostString is not None:
                        feeCurrencyCode = 'PT'
            fee = {
                'cost': feeCostString,
                'rate': feeRateString,
                'currency': feeCurrencyCode,
            }
        return self.safe_trade({
            'info': trade,
            'id': id,
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'order': orderId,
            'type': type,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
        }, market)

    def parse_spot_balance(self, response):
        #
        #     {
        #         "code":0,
        #         "msg":"",
        #         "data":[
        #             {
        #                 "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 = None
        result: dict = {'info': response}
        data = self.safe_value(response, 'data', [])
        for i in range(0, len(data)):
            balance = data[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            currency = self.safe_value(self.currencies, code, {})
            scale = self.safe_integer(currency, 'valueScale', 8)
            account = self.account()
            balanceEv = self.safe_string(balance, 'balanceEv')
            lockedTradingBalanceEv = self.safe_string(balance, 'lockedTradingBalanceEv')
            lockedWithdrawEv = self.safe_string(balance, 'lockedWithdrawEv')
            total = self.from_en(balanceEv, scale)
            lockedTradingBalance = self.from_en(lockedTradingBalanceEv, scale)
            lockedWithdraw = self.from_en(lockedWithdrawEv, scale)
            used = Precise.string_add(lockedTradingBalance, lockedWithdraw)
            lastUpdateTimeNs = self.safe_integer_product(balance, 'lastUpdateTimeNs', 0.000001)
            timestamp = lastUpdateTimeNs if (timestamp is None) else max(timestamp, lastUpdateTimeNs)
            account['total'] = total
            account['used'] = used
            result[code] = account
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        return self.safe_balance(result)

    def parse_swap_balance(self, response):
        # usdt
        #   {
        #       "info": {
        #         "code": "0",
        #         "msg": '',
        #         "data": {
        #           "account": {
        #             "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: dict = {'info': response}
        data = self.safe_value(response, 'data', {})
        balance = self.safe_value(data, 'account', {})
        currencyId = self.safe_string(balance, 'currency')
        code = self.safe_currency_code(currencyId)
        currency = self.currency(code)
        valueScale = self.safe_integer(currency, 'valueScale', 8)
        account = self.account()
        accountBalanceEv = self.safe_string_2(balance, 'accountBalanceEv', 'accountBalanceRv')
        totalUsedBalanceEv = self.safe_string_2(balance, 'totalUsedBalanceEv', 'totalUsedBalanceRv')
        needsConversion = (code != 'USDT')
        account['total'] = self.from_en(accountBalanceEv, valueScale) if needsConversion else accountBalanceEv
        account['used'] = self.from_en(totalUsedBalanceEv, valueScale) if needsConversion else totalUsedBalanceEv
        result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders

        https://phemex-docs.github.io/#query-wallets
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query-account-positions
        https://phemex-docs.github.io/#query-trading-account-and-positions

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: spot or swap
        :param str [params.code]: *swap only* currency code of the balance to query(USD, USDT, etc), default is USDT
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        self.load_markets()
        type = None
        type, params = self.handle_market_type_and_params('fetchBalance', None, params)
        code = self.safe_string(params, 'code')
        params = self.omit(params, ['code'])
        response = None
        request: dict = {}
        if (type != 'spot') and (type != 'swap'):
            raise BadRequest(self.id + ' does not support ' + type + ' markets, only spot and swap')
        if type == 'swap':
            settle = None
            settle, params = self.handle_option_and_params(params, 'fetchBalance', 'settle', 'USDT')
            if code is not None or settle is not None:
                coin = None
                if code is not None:
                    coin = code
                else:
                    coin = settle
                currency = self.currency(coin)
                request['currency'] = currency['id']
                if currency['id'] == 'USDT':
                    response = self.privateGetGAccountsAccountPositions(self.extend(request, params))
                else:
                    response = self.privateGetAccountsAccountPositions(self.extend(request, params))
            else:
                currency = self.safe_string(params, 'currency')
                if currency is None:
                    raise ArgumentsRequired(self.id + ' fetchBalance() requires a code parameter or a currency or settle parameter for ' + type + ' type')
                response = self.privateGetSpotWallets(self.extend(request, params))
        else:
            response = self.privateGetSpotWallets(self.extend(request, params))
        #
        # usdt
        #   {
        #       "info": {
        #         "code": "0",
        #         "msg": '',
        #         "data": {
        #           "account": {
        #             "userID": "940666",
        #             "accountId": "9406660003",
        #             "currency": "USDT",
        #             "accountBalanceRv": "99.93143972",
        #             "totalUsedBalanceRv": "0.40456",
        #             "bonusBalanceRv": "0"
        #           },
        #   }
        #
        # spot
        #
        #     {
        #         "code":0,
        #         "msg":"",
        #         "data":[
        #             {
        #                 "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":{
        #                 "accountId":6192120001,
        #                 "currency":"BTC",
        #                 "accountBalanceEv":1254744,
        #                 "totalUsedBalanceEv":0,
        #                 "bonusBalanceEv":1254744
        #             },
        #             "positions":[
        #                 {
        #                     "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 self.parse_swap_balance(response)
        return self.parse_spot_balance(response)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            '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 self.safe_string(statuses, status, status)

    def parse_order_type(self, type: Str):
        types: dict = {
            '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 self.safe_string(types, type, type)

    def parse_time_in_force(self, timeInForce: Str):
        timeInForces: dict = {
            'GoodTillCancel': 'GTC',
            'PostOnly': 'PO',
            'ImmediateOrCancel': 'IOC',
            'FillOrKill': 'FOK',
        }
        return self.safe_string(timeInForces, timeInForce, timeInForce)

    def parse_spot_order(self, order: dict, market: Market = None):
        #
        # 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 = self.safe_string(order, 'orderID')
        clientOrderId = self.safe_string(order, 'clOrdID')
        if (clientOrderId is not None) and (len(clientOrderId) < 1):
            clientOrderId = None
        marketId = self.safe_string(order, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        price = self.from_ep(self.safe_string(order, 'priceEp'), market)
        amount = self.from_ev(self.safe_string(order, 'baseQtyEv'), market)
        remaining = self.omit_zero(self.from_ev(self.safe_string(order, 'leavesBaseQtyEv'), market))
        filled = self.from_ev(self.safe_string_2(order, 'cumBaseQtyEv', 'cumBaseValueEv'), market)
        cost = self.from_er(self.safe_string_2(order, 'cumQuoteValueEv', 'quoteQtyEv'), market)
        average = self.from_ep(self.safe_string(order, 'avgPriceEp'), market)
        status = self.parse_order_status(self.safe_string(order, 'ordStatus'))
        side = self.safe_string_lower(order, 'side')
        type = self.parse_order_type(self.safe_string(order, 'ordType'))
        timestamp = self.safe_integer_product_2(order, 'actionTimeNs', 'createTimeNs', 0.000001)
        fee = None
        feeCost = self.from_ev(self.safe_string(order, 'cumFeeEv'), market)
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': self.safe_currency_code(self.safe_string(order, 'feeCurrency')),
            }
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
        triggerPrice = self.parse_number(self.omit_zero(self.from_ep(self.safe_string(order, 'stopPxEp'))))
        postOnly = (timeInForce == 'PO')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            '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': None,
        }, market)

    def parse_order_side(self, side):
        sides: dict = {
            '1': 'buy',
            '2': 'sell',
        }
        return self.safe_string(sides, side, side)

    def parse_swap_order(self, order, market: Market = None):
        #
        #     {
        #         "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 = self.safe_string_2(order, 'orderID', 'orderId')
        clientOrderId = self.safe_string_2(order, 'clOrdID', 'clOrdId')
        if (clientOrderId is not None) and (len(clientOrderId) < 1):
            clientOrderId = None
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        market = self.safe_market(marketId, market)
        status = self.parse_order_status(self.safe_string(order, 'ordStatus'))
        side = self.parse_order_side(self.safe_string_lower(order, 'side'))
        type = self.parse_order_type(self.safe_string(order, 'orderType'))
        price = self.safe_string(order, 'priceRp')
        if price is None:
            price = self.from_ep(self.safe_string(order, 'priceEp'), market)
        amount = self.safe_number_2(order, 'orderQty', 'orderQtyRq')
        filled = self.safe_number_2(order, 'cumQty', 'cumQtyRq')
        remaining = self.safe_number_2(order, 'leavesQty', 'leavesQtyRq')
        timestamp = self.safe_integer_product(order, 'actionTimeNs', 0.000001)
        if timestamp is None:
            timestamp = self.safe_integer(order, 'createdAt')
        cost = self.safe_number_2(order, 'cumValue', 'cumValueRv')
        lastTradeTimestamp = self.safe_integer_product(order, 'transactTimeNs', 0.000001)
        if lastTradeTimestamp == 0:
            lastTradeTimestamp = None
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'timeInForce'))
        triggerPrice = self.omit_zero(self.safe_string_2(order, 'stopPx', 'stopPxRp'))
        postOnly = (timeInForce == 'PO')
        reduceOnly = self.safe_value(order, 'reduceOnly')
        execInst = self.safe_string(order, 'execInst')
        if execInst == 'ReduceOnly':
            reduceOnly = True
        takeProfit = self.safe_string(order, 'takeProfitRp')
        stopLoss = self.safe_string(order, 'stopLossRp')
        feeValue = self.omit_zero(self.safe_string(order, 'execFeeRv'))
        ptFeeRv = self.omit_zero(self.safe_string(order, 'ptFeeRv'))
        fee = None
        if feeValue is not None:
            fee = {
                'cost': feeValue,
                'currency': market['quote'],
            }
        elif ptFeeRv is not None:
            fee = {
                'cost': ptFeeRv,
                'currency': 'PT',
            }
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'datetime': self.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': None,
            'status': status,
            'fee': fee,
            'trades': None,
        })

    def parse_order(self, order: dict, market: Market = None) -> Order:
        isSwap = self.safe_bool(market, 'swap', False)
        hasPnl = ('closedPnl' in order) or ('closedPnlRv' in order) or ('totalPnlRv' in order)
        if isSwap or hasPnl:
            return self.parse_swap_order(order, market)
        return self.parse_spot_order(order, market)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#place-order
        https://phemex-docs.github.io/#place-order-http-put-prefered-3

        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.trigger]: trigger price for conditional orders
        :param dict [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 dict [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 str [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
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        requestSide = self.capitalize(side)
        type = self.capitalize(type)
        request: dict = {
            # common
            'symbol': market['id'],
            'side': requestSide,  # Sell, Buy
            'ordType': type,  # Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched(additionally for contract-markets: MarketAsLimit, StopAsLimit, MarketIfTouchedAsLimit)
            # 'stopPxEp': self.to_ep(stopPx, market),  # for conditional orders
            # 'priceEp': self.to_ep(price, market),  # required for limit orders
            # 'timeInForce': 'GoodTillCancel',  # GoodTillCancel, PostOnly, ImmediateOrCancel, FillOrKill
            # ----------------------------------------------------------------
            # spot
            # 'qtyType': 'ByBase',  # ByBase, ByQuote
            # 'quoteQtyEv': self.to_ep(cost, market),
            # 'baseQtyEv': self.to_ev(amount, market),
            # 'trigger': 'ByLastPrice',  # required for conditional orders
            # ----------------------------------------------------------------
            # swap
            # 'clOrdID': self.uuid(),  # max length 40
            # 'orderQty': self.amount_to_precision(amount, symbol),
            # 'reduceOnly': False,
            # 'closeOnTrigger': False,  # implicit reduceOnly and cancel other orders in the same direction
            # 'takeProfitEp': self.to_ep(takeProfit, market),
            # 'stopLossEp': self.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 = self.safe_string_2(params, 'clOrdID', 'clientOrderId')
        stopLoss = self.safe_value(params, 'stopLoss')
        stopLossDefined = (stopLoss is not None)
        takeProfit = self.safe_value(params, 'takeProfit')
        takeProfitDefined = (takeProfit is not None)
        if clientOrderId is None:
            brokerId = self.safe_string(self.options, 'brokerId', 'CCXT123456')
            if brokerId is not None:
                request['clOrdID'] = brokerId + self.uuid16()
        else:
            request['clOrdID'] = clientOrderId
            params = self.omit(params, ['clOrdID', 'clientOrderId'])
        triggerPrice = self.safe_string_n(params, ['stopPx', 'stopPrice', 'triggerPrice'])
        if triggerPrice is not None:
            if market['settle'] == 'USDT':
                request['stopPxRp'] = self.price_to_precision(symbol, triggerPrice)
            else:
                request['stopPxEp'] = self.to_ep(triggerPrice, market)
        params = self.omit(params, ['stopPx', 'stopPrice', 'stopLoss', 'takeProfit', 'triggerPrice'])
        if market['spot']:
            qtyType = self.safe_value(params, 'qtyType', 'ByBase')
            if (type == 'Market') or (type == 'Stop') or (type == 'MarketIfTouched'):
                if price is not None:
                    qtyType = 'ByQuote'
            if triggerPrice is not None:
                if type == 'Limit':
                    request['ordType'] = 'StopLimit'
                elif type == 'Market':
                    request['ordType'] = 'Stop'
                request['trigger'] = 'ByLastPrice'
            request['qtyType'] = qtyType
            if qtyType == 'ByQuote':
                cost = self.safe_number(params, 'cost')
                params = self.omit(params, 'cost')
                if self.options['createOrderByQuoteRequiresPrice']:
                    if price is not None:
                        amountString = self.number_to_string(amount)
                        priceString = self.number_to_string(price)
                        quoteAmount = Precise.string_mul(amountString, priceString)
                        cost = self.parse_number(quoteAmount)
                    elif cost is None:
                        raise ArgumentsRequired(self.id + ' createOrder() ' + qtyType + ' requires a price argument or a cost parameter')
                cost = amount if (cost is None) else cost
                costString = self.number_to_string(cost)
                request['quoteQtyEv'] = self.to_ev(costString, market)
            else:
                amountString = self.number_to_string(amount)
                request['baseQtyEv'] = self.to_ev(amountString, market)
        elif market['swap']:
            hedged = self.safe_bool(params, 'hedged', False)
            params = self.omit(params, 'hedged')
            posSide = self.safe_string_lower(params, 'posSide')
            if posSide is None:
                if hedged:
                    reduceOnly = self.safe_bool(params, 'reduceOnly')
                    if reduceOnly:
                        side = 'sell' if (side == 'buy') else 'buy'
                        params = self.omit(params, 'reduceOnly')
                    posSide = 'Long' if (side == 'buy') else 'Short'
                else:
                    posSide = 'Merged'
            posSide = self.capitalize(posSide)
            request['posSide'] = posSide
            if market['settle'] == 'USDT':
                request['orderQtyRq'] = amount
            else:
                request['orderQty'] = self.parse_to_int(amount)
            if triggerPrice is not None:
                triggerType = self.safe_string(params, 'triggerType', 'ByMarkPrice')
                request['triggerType'] = triggerType
                # set direction & exchange specific order type
                triggerDirection = None
                triggerDirection, params = self.handle_param_string(params, 'triggerDirection')
                if triggerDirection is None:
                    raise ArgumentsRequired(self.id + " createOrder() also requires a 'triggerDirection' parameter with either 'up' or 'down' value")
                # the flow defined per https://phemex-docs.github.io/#more-order-type-examples
                if triggerDirection == 'up':
                    if side == 'sell':
                        request['ordType'] = 'MarketIfTouched' if (type == 'Market') else 'LimitIfTouched'
                    elif side == 'buy':
                        request['ordType'] = 'Stop' if (type == 'Market') else 'StopLimit'
                elif triggerDirection == 'down':
                    if side == 'sell':
                        request['ordType'] = 'Stop' if (type == 'Market') else 'StopLimit'
                    elif side == 'buy':
                        request['ordType'] = 'MarketIfTouched' if (type == 'Market') else 'LimitIfTouched'
            if stopLossDefined or takeProfitDefined:
                if stopLossDefined:
                    stopLossTriggerPrice = self.safe_value_2(stopLoss, 'triggerPrice', 'stopPrice')
                    if stopLossTriggerPrice is None:
                        raise InvalidOrder(self.id + ' createOrder() requires a trigger price in params["stopLoss"]["triggerPrice"] for a stop loss order')
                    if market['settle'] == 'USDT':
                        request['stopLossRp'] = self.price_to_precision(symbol, stopLossTriggerPrice)
                    else:
                        request['stopLossEp'] = self.to_ep(stopLossTriggerPrice, market)
                    stopLossTriggerPriceType = self.safe_string_2(stopLoss, 'triggerPriceType', 'slTrigger')
                    if stopLossTriggerPriceType is not None:
                        request['slTrigger'] = self.safe_string(self.options['triggerPriceTypesMap'], stopLossTriggerPriceType, stopLossTriggerPriceType)
                    slLimitPrice = self.safe_string(stopLoss, 'price')
                    if slLimitPrice is not None:
                        request['slPxRp'] = self.price_to_precision(symbol, slLimitPrice)
                if takeProfitDefined:
                    takeProfitTriggerPrice = self.safe_value_2(takeProfit, 'triggerPrice', 'stopPrice')
                    if takeProfitTriggerPrice is None:
                        raise InvalidOrder(self.id + ' createOrder() requires a trigger price in params["takeProfit"]["triggerPrice"] for a take profit order')
                    if market['settle'] == 'USDT':
                        request['takeProfitRp'] = self.price_to_precision(symbol, takeProfitTriggerPrice)
                    else:
                        request['takeProfitEp'] = self.to_ep(takeProfitTriggerPrice, market)
                    takeProfitTriggerPriceType = self.safe_string_2(takeProfit, 'triggerPriceType', 'tpTrigger')
                    if takeProfitTriggerPriceType is not None:
                        request['tpTrigger'] = self.safe_string(self.options['triggerPriceTypesMap'], takeProfitTriggerPriceType, takeProfitTriggerPriceType)
                    tpLimitPrice = self.safe_string(takeProfit, 'price')
                    if tpLimitPrice is not None:
                        request['tpPxRp'] = self.price_to_precision(symbol, tpLimitPrice)
        if (type == 'Limit') or (type == 'StopLimit') or (type == 'LimitIfTouched'):
            if market['settle'] == 'USDT':
                request['priceRp'] = self.price_to_precision(symbol, price)
            else:
                priceString = self.number_to_string(price)
                request['priceEp'] = self.to_ep(priceString, market)
        takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
        if takeProfitPrice is not None:
            if market['settle'] == 'USDT':
                request['takeProfitRp'] = self.price_to_precision(symbol, takeProfitPrice)
            else:
                request['takeProfitEp'] = self.to_ep(takeProfitPrice, market)
            params = self.omit(params, 'takeProfitPrice')
        stopLossPrice = self.safe_string(params, 'stopLossPrice')
        if stopLossPrice is not None:
            if market['settle'] == 'USDT':
                request['stopLossRp'] = self.price_to_precision(symbol, stopLossPrice)
            else:
                request['stopLossEp'] = self.to_ep(stopLossPrice, market)
            params = self.omit(params, 'stopLossPrice')
        response = None
        if market['settle'] == 'USDT':
            response = self.privatePostGOrders(self.extend(request, params))
        elif market['contract']:
            response = self.privatePostOrders(self.extend(request, params))
        else:
            response = self.privatePostSpotOrders(self.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 = self.safe_dict(response, 'data', {})
        return self.parse_order(data, market)

    def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
        """
        edit a trade order

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#amend-order-by-orderid

        :param str id: cancel order id
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.posSide]: either 'Merged' or 'Long' or 'Short'
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clOrdID')
        params = self.omit(params, ['clientOrderId', 'clOrdID'])
        isUSDTSettled = (market['settle'] == 'USDT')
        if clientOrderId is not None:
            request['clOrdID'] = clientOrderId
        else:
            request['orderID'] = id
        if price is not None:
            if isUSDTSettled:
                request['priceRp'] = self.price_to_precision(market['symbol'], price)
            else:
                request['priceEp'] = self.to_ep(price, market)
        # Note the uppercase 'V' in 'baseQtyEV' request. that is exchange's requirement at self moment. However, to avoid mistakes from user side, let's support lowercased 'baseQtyEv' too
        finalQty = self.safe_string(params, 'baseQtyEv')
        params = self.omit(params, ['baseQtyEv'])
        if finalQty is not None:
            request['baseQtyEV'] = finalQty
        elif amount is not None:
            if isUSDTSettled:
                request['orderQtyRq'] = self.amount_to_precision(market['symbol'], amount)
            else:
                request['baseQtyEV'] = self.to_ev(amount, market)
        triggerPrice = self.safe_string_n(params, ['triggerPrice', 'stopPx', 'stopPrice'])
        if triggerPrice is not None:
            if isUSDTSettled:
                request['stopPxRp'] = self.price_to_precision(symbol, triggerPrice)
            else:
                request['stopPxEp'] = self.to_ep(triggerPrice, market)
        params = self.omit(params, ['triggerPrice', 'stopPx', 'stopPrice'])
        response = None
        if isUSDTSettled:
            posSide = self.safe_string(params, 'posSide')
            if posSide is None:
                request['posSide'] = 'Merged'
            response = self.privatePutGOrdersReplace(self.extend(request, params))
        elif market['swap']:
            response = self.privatePutOrdersReplace(self.extend(request, params))
        else:
            response = self.privatePutSpotOrders(self.extend(request, params))
        data = self.safe_dict(response, 'data', {})
        return self.parse_order(data, market)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#cancel-single-order-by-orderid

        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.posSide]: either 'Merged' or 'Long' or 'Short'
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clOrdID')
        params = self.omit(params, ['clientOrderId', 'clOrdID'])
        if clientOrderId is not None:
            request['clOrdID'] = clientOrderId
        else:
            request['orderID'] = id
        response = None
        if market['settle'] == 'USDT':
            posSide = self.safe_string(params, 'posSide')
            if posSide is None:
                request['posSide'] = 'Merged'
            response = self.privateDeleteGOrdersCancel(self.extend(request, params))
        elif market['swap']:
            response = self.privateDeleteOrdersCancel(self.extend(request, params))
        else:
            response = self.privateDeleteSpotOrders(self.extend(request, params))
        data = self.safe_dict(response, 'data', {})
        return self.parse_order(data, market)

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders in a market

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#cancelall

        :param str symbol: unified market symbol of the market to cancel orders in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        trigger = self.safe_value_2(params, 'stop', 'trigger', False)
        params = self.omit(params, ['stop', 'trigger'])
        request: dict = {
            '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 = None
        if market['settle'] == 'USDT':
            response = self.privateDeleteGOrdersAll(self.extend(request, params))
            #
            #    {
            #        code: '0',
            #        msg: '',
            #        data: '1'
            #    }
            #
        elif market['swap']:
            response = self.privateDeleteOrdersAll(self.extend(request, params))
            #
            #    {
            #        code: '0',
            #        msg: '',
            #        data: '1'
            #    }
            #
        else:
            response = self.privateDeleteSpotOrdersAll(self.extend(request, params))
            #
            #    {
            #        code: '0',
            #        msg: '',
            #        data: {
            #            total: '1'
            #        }
            #    }
            #
        return [
            self.safe_order({
                'info': response,
            }),
        ]

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """

        https://phemex-docs.github.io/#query-orders-by-ids

        fetches information on an order made by the user
        :param str id: the order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clOrdID')
        params = self.omit(params, ['clientOrderId', 'clOrdID'])
        if clientOrderId is not None:
            request['clOrdID'] = clientOrderId
        else:
            request['orderID'] = id
        response = None
        if market['settle'] == 'USDT':
            response = self.privateGetApiDataGFuturesOrdersByOrderId(self.extend(request, params))
        elif market['spot']:
            response = self.privateGetApiDataSpotsOrdersByOrderId(self.extend(request, params))
        else:
            response = self.privateGetExchangeOrder(self.extend(request, params))
        data = self.safe_value(response, 'data', {})
        order = data
        if isinstance(data, list):
            numOrders = len(data)
            if numOrders < 1:
                if clientOrderId is not None:
                    raise OrderNotFound(self.id + ' fetchOrder() ' + symbol + ' order with clientOrderId ' + clientOrderId + ' not found')
                else:
                    raise OrderNotFound(self.id + ' fetchOrder() ' + symbol + ' order with id ' + id + ' not found')
            order = self.safe_dict(data, 0, {})
        elif market['spot']:
            rows = self.safe_list(data, 'rows', [])
            order = self.safe_dict(rows, 0, {})
        return self.parse_order(order, market)

    def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple orders made by the user

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryorder

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        if since is not None:
            request['start'] = since
        if limit is not None:
            request['limit'] = limit
        response = None
        if market['settle'] == 'USDT':
            request['currency'] = market['settle']
            response = self.privateGetExchangeOrderV2OrderList(self.extend(request, params))
        elif market['swap']:
            response = self.privateGetExchangeOrderList(self.extend(request, params))
        else:
            response = self.privateGetApiDataSpotsOrders(self.extend(request, params))
        data = self.safe_value(response, 'data', {})
        rows = self.safe_list(data, 'rows', data)
        return self.parse_orders(rows, market, since, limit)

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryopenorder
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#spotListAllOpenOrder

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        response = None
        try:
            if market['settle'] == 'USDT':
                response = self.privateGetGOrdersActiveList(self.extend(request, params))
            elif market['swap']:
                response = self.privateGetOrdersActiveList(self.extend(request, params))
            else:
                response = self.privateGetSpotOrders(self.extend(request, params))
        except Exception as e:
            if isinstance(e, OrderNotFound):
                return []
            raise e
        data = self.safe_value(response, 'data', {})
        if isinstance(data, list):
            return self.parse_orders(data, market, since, limit)
        else:
            rows = self.safe_list(data, 'rows', [])
            return self.parse_orders(rows, market, since, limit)

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryorder
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#queryorder
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedgedd-Perpetual-API.md#query-closed-orders-by-symbol
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#spotDataOrdersByIds

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.settle]: the settlement currency to fetch orders for
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {
        }
        if market is not None:
            request['symbol'] = market['id']
        if since is not None:
            request['start'] = since
        if limit is not None:
            request['limit'] = limit
        response = None
        if (symbol is None) or (self.safe_string(market, 'settle') == 'USDT'):
            request['currency'] = self.safe_string(params, 'settle', 'USDT')
            response = self.privateGetExchangeOrderV2OrderList(self.extend(request, params))
        elif market['swap']:
            response = self.privateGetExchangeOrderList(self.extend(request, params))
        else:
            response = self.privateGetExchangeSpotOrder(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "code":0,
        #         "msg":"OK",
        #         "data":{
        #             "total":8,
        #             "rows":[
        #                 {
        #                     "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 = self.safe_value(response, 'data', {})
        if isinstance(data, list):
            return self.parse_orders(data, market, since, limit)
        else:
            rows = self.safe_list(data, 'rows', [])
            return self.parse_orders(rows, market, since, limit)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#query-user-trade
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query-user-trade
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Spot-API-en.md#spotDataTradesHist

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {}
        if limit is not None:
            limit = min(200, limit)
            request['limit'] = limit
        isUSDTSettled = (symbol is None) or (self.safe_string(market, 'settle') == 'USDT')
        if isUSDTSettled:
            request['currency'] = 'USDT'
            request['offset'] = 0
            if limit is None:
                request['limit'] = 200
        else:
            request['symbol'] = market['id']
        if since is not None:
            request['start'] = since
        response = None
        if isUSDTSettled:
            response = self.privateGetExchangeOrderV2TradingList(self.extend(request, params))
        elif market['swap']:
            response = self.privateGetExchangeOrderTrade(self.extend(request, params))
        else:
            response = self.privateGetExchangeSpotOrderTrades(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "code": 0,
        #         "msg": "OK",
        #         "data": {
        #             "total": 1,
        #             "rows": [
        #                 {
        #                     "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": [
        #                 {
        #                     "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": [
        #             {
        #                 "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 = None
        if isUSDTSettled:
            data = self.safe_value(response, 'data', [])
        else:
            data = self.safe_value(response, 'data', {})
            data = self.safe_value(data, 'rows', [])
        return self.parse_trades(data, market, since, limit)

    def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
        """
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        defaultNetworks = self.safe_dict(self.options, 'defaultNetworks')
        defaultNetwork = self.safe_string_upper(defaultNetworks, code)
        networks = self.safe_dict(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network', defaultNetwork)
        network = self.safe_string(networks, network, network)
        if network is None:
            request['chainName'] = currency['id']
        else:
            request['chainName'] = network
            params = self.omit(params, 'network')
        response = self.privateGetPhemexUserWalletsV2DepositAddress(self.extend(request, params))
        #     {
        #         "code":0,
        #         "msg":"OK",
        #         "data":{
        #             "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad",
        #             "tag":null
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        address = self.safe_string(data, 'address')
        tag = self.safe_string(data, 'tag')
        self.check_address(address)
        return {
            'info': response,
            'currency': code,
            'network': None,
            'address': address,
            'tag': tag,
        }

    def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = self.privateGetExchangeWalletsDepositList(params)
        #
        #     {
        #         "code":0,
        #         "msg":"OK",
        #         "data":[
        #             {
        #                 "id":29200,
        #                 "currency":"USDT",
        #                 "currencyCode":3,
        #                 "txHash":"0x0bdbdc47807769a03b158d5753f54dfc58b92993d2f5e818db21863e01238e5d",
        #                 "address":"0x5bfbf60e0fa7f63598e6cfd8a7fd3ffac4ccc6ad",
        #                 "amountEv":3000000000,
        #                 "confirmations":13,
        #                 "type":"Deposit",
        #                 "status":"Success",
        #                 "createdAt":1592722565000
        #             }
        #         ]
        #     }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit)

    def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        currency = None
        if code is not None:
            currency = self.currency(code)
        response = self.privateGetExchangeWalletsWithdrawList(params)
        #
        #     {
        #         "code":0,
        #         "msg":"OK",
        #         "data":[
        #             {
        #                 "address": "1Lxxxxxxxxxxx"
        #                 "amountEv": 200000
        #                 "currency": "BTC"
        #                 "currencyCode": 1
        #                 "expiredTime": 0
        #                 "feeEv": 50000
        #                 "rejectReason": null
        #                 "status": "Succeed"
        #                 "txHash": "44exxxxxxxxxxxxxxxxxxxxxx"
        #                 "withdrawStatus: ""
        #             }
        #         ]
        #     }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_transactions(data, currency, since, limit)

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            '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 self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # 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 = self.safe_string(transaction, 'id')
        address = self.safe_string(transaction, 'address')
        tag = None
        txid = self.safe_string(transaction, 'txHash')
        currencyId = self.safe_string(transaction, 'currency')
        currency = self.safe_currency(currencyId, currency)
        code = currency['code']
        networkId = self.safe_string(transaction, 'chainName')
        timestamp = self.safe_integer_n(transaction, ['createdAt', 'submitedAt', 'submittedAt'])
        type = self.safe_string_lower(transaction, 'type')
        feeCost = self.parse_number(self.from_en(self.safe_string(transaction, 'feeEv'), currency['valueScale']))
        if feeCost is None:
            feeCost = self.safe_number(transaction, 'feeRv')
        fee = None
        if feeCost is not None:
            type = 'withdrawal'
            fee = {
                'cost': feeCost,
                'currency': code,
            }
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        amount = self.parse_number(self.from_en(self.safe_string(transaction, 'amountEv'), currency['valueScale']))
        if amount is None:
            amount = self.safe_number(transaction, 'amountRv')
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': self.network_id_to_code(networkId),
            'address': address,
            'addressTo': address,
            'addressFrom': None,
            'tag': tag,
            'tagTo': tag,
            'tagFrom': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'comment': None,
            'internal': None,
            'fee': fee,
        }

    def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
        """
        fetch all open positions

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#query-trading-account-and-positions
        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#query-account-positions
        https://phemex-docs.github.io/#query-account-positions-with-unrealized-pnl

        :param str[] [symbols]: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.code]: the currency code to fetch positions for, USD, BTC or USDT, USDT is the default
        :param str [params.method]: *USDT contracts only* 'privateGetGAccountsAccountPositions' or 'privateGetAccountsPositions' default is 'privateGetGAccountsAccountPositions'
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        subType = None
        code = self.safe_string_2(params, 'currency', 'code', 'USDT')
        params = self.omit(params, ['currency', 'code'])
        settle = None
        market = None
        firstSymbol = self.safe_string(symbols, 0)
        if firstSymbol is not None:
            market = self.market(firstSymbol)
            settle = market['settle']
            code = market['settle']
        else:
            settle, params = self.handle_option_and_params(params, 'fetchPositions', 'settle', code)
        subType, params = self.handle_sub_type_and_params('fetchPositions', market, params)
        isUSDTSettled = settle == 'USDT'
        if isUSDTSettled:
            code = 'USDT'
        elif settle == 'BTC':
            code = 'BTC'
        elif code is None:
            code = 'USD' if (subType == 'linear') else 'BTC'
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        response = None
        if isUSDTSettled:
            method = None
            method, params = self.handle_option_and_params(params, 'fetchPositions', 'method', 'privateGetGAccountsAccountPositions')
            if method == 'privateGetGAccountsAccountPositions':
                response = self.privateGetGAccountsAccountPositions(self.extend(request, params))
            else:
                response = self.privateGetAccountsPositions(self.extend(request, params))
        else:
            response = self.privateGetAccountsAccountPositions(self.extend(request, params))
        #
        #     {
        #         "code":0,"msg":"",
        #         "data":{
        #             "account":{
        #                 "accountId":6192120001,
        #                 "currency":"BTC",
        #                 "accountBalanceEv":1254744,
        #                 "totalUsedBalanceEv":0,
        #                 "bonusBalanceEv":1254744
        #             },
        #             "positions":[
        #                 {
        #                     "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 = self.safe_value(response, 'data', {})
        positions = self.safe_value(data, 'positions', [])
        result = []
        for i in range(0, len(positions)):
            position = positions[i]
            result.append(self.parse_position(position))
        return self.filter_by_array_positions(result, 'symbol', symbols, False)

    def parse_position(self, position: dict, market: Market = None):
        #
        #    {
        #        "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 = self.safe_string(position, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        collateral = self.safe_string_2(position, 'positionMargin', 'positionMarginRv')
        notionalString = self.safe_string_2(position, 'value', 'valueRv')
        maintenanceMarginPercentageString = self.safe_string_2(position, 'maintMarginReq', 'maintMarginReqRr')
        maintenanceMarginString = Precise.string_mul(notionalString, maintenanceMarginPercentageString)
        initialMarginString = self.safe_string_2(position, 'assignedPosBalance', 'assignedPosBalanceRv')
        initialMarginPercentageString = Precise.string_div(initialMarginString, notionalString)
        liquidationPrice = self.safe_number_2(position, 'liquidationPrice', 'liquidationPriceRp')
        markPriceString = self.safe_string_2(position, 'markPrice', 'markPriceRp')
        contracts = self.safe_string(position, 'size')
        contractSize = self.safe_value(market, 'contractSize')
        contractSizeString = self.number_to_string(contractSize)
        leverage = self.parse_number(Precise.string_abs((self.safe_string_2(position, 'leverage', 'leverageRr'))))
        entryPriceString = self.safe_string_2(position, 'avgEntryPrice', 'avgEntryPriceRp')
        rawSide = self.safe_string(position, 'side')
        side = None
        if rawSide is not None:
            side = 'long' if (rawSide == 'Buy') else 'short'
        priceDiff = None
        currency = self.safe_string(position, 'currency')
        if currency == 'USD':
            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)
        marginRatio = Precise.string_div(maintenanceMarginString, collateral)
        isCross = self.safe_value(position, 'crossMargin')
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': symbol,
            'contracts': self.parse_number(contracts),
            'contractSize': contractSize,
            'unrealizedPnl': self.parse_number(unrealizedPnl),
            'leverage': leverage,
            'liquidationPrice': liquidationPrice,
            'collateral': self.parse_number(collateral),
            'notional': self.parse_number(notionalString),
            'markPrice': self.parse_number(markPriceString),  # markPrice lags a bit ¯\_(ツ)_/¯
            'lastPrice': None,
            'entryPrice': self.parse_number(entryPriceString),
            'timestamp': None,
            'lastUpdateTimestamp': None,
            'initialMargin': self.parse_number(initialMarginString),
            'initialMarginPercentage': self.parse_number(initialMarginPercentageString),
            'maintenanceMargin': self.parse_number(maintenanceMarginString),
            'maintenanceMarginPercentage': self.parse_number(maintenanceMarginPercentageString),
            'marginRatio': self.parse_number(marginRatio),
            'datetime': None,
            'marginMode': 'cross' if isCross else 'isolated',
            'side': side,
            'hedged': False,
            'percentage': None,
            'stopLossPrice': None,
            'takeProfitPrice': None,
        })

    def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch the history of funding payments paid and received on self account

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#futureDataFundingFeesHist

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `funding history structure <https://docs.ccxt.com/#/?id=funding-history-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchFundingHistory() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            # 'limit': 20,  # Page size default 20, max 200
            # 'offset': 0,  # Page start default 0
        }
        if limit is not None:
            if limit > 200:
                raise BadRequest(self.id + ' fetchFundingHistory() limit argument cannot exceed 200')
            request['limit'] = limit
        response = None
        isUsdt = market['settle'] == 'USDT'
        if isUsdt:
            response = self.privateGetApiDataGFuturesFundingFees(self.extend(request, params))
        else:
            response = self.privateGetApiDataFuturesFundingFees(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "OK",
        #         "data": {
        #             "rows": [
        #                 {
        #                     "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 = self.safe_value(response, 'data', {})
        rows = self.safe_value(data, 'rows', [])
        result = []
        for i in range(0, len(rows)):
            entry = rows[i]
            timestamp = self.safe_integer(entry, 'createTime')
            execFee = self.safe_string_2(entry, 'execFeeEv', 'execFeeRv')
            currencyCode = self.safe_currency_code(self.safe_string(entry, 'currency'))
            result.append({
                'info': entry,
                'symbol': self.safe_string(entry, 'symbol'),
                'code': currencyCode,
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
                'id': None,
                'amount': self.parse_funding_fee_to_precision(execFee, market, currencyCode),
            })
        return result

    def parse_funding_fee_to_precision(self, value, market: Market = None, currencyCode: Str = None):
        if value is None or currencyCode is None:
            return value
        # it was confirmed by phemex support, that USDT contracts use direct amounts in funding fees, while USD & INVERSE needs 'valueScale'
        isUsdt = market['settle'] == 'USDT'
        if not isUsdt:
            currency = self.safe_currency(currencyCode)
            scale = self.safe_string(currency['info'], 'valueScale')
            tickPrecision = self.parse_precision(scale)
            value = Precise.string_mul(value, tickPrecision)
        return value

    def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
        """
        fetch the current funding rate
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['swap']:
            raise BadSymbol(self.id + ' fetchFundingRate() supports swap contracts only')
        request: dict = {
            'symbol': market['id'],
        }
        response: dict = {}
        if not market['linear']:
            response = self.v1GetMdTicker24hr(self.extend(request, params))
        else:
            response = self.v2GetMdV2Ticker24hr(self.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 = self.safe_value(response, 'result', {})
        return self.parse_funding_rate(result, market)

    def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
        #
        #     {
        #         "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 = self.safe_string(contract, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_integer_product(contract, 'timestamp', 0.000001)
        markEp = self.from_ep(self.safe_string(contract, 'markEp'), market)
        indexEp = self.from_ep(self.safe_string(contract, 'indexEp'), market)
        fundingRateEr = self.from_er(self.safe_string(contract, 'fundingRateEr'), market)
        nextFundingRateEr = self.from_er(self.safe_string(contract, 'predFundingRateEr'), market)
        return {
            'info': contract,
            'symbol': symbol,
            'markPrice': self.safe_number(contract, 'markPriceRp', markEp),
            'indexPrice': self.safe_number(contract, 'indexPriceRp', indexEp),
            'interestRate': None,
            'estimatedSettlePrice': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fundingRate': self.safe_number(contract, 'fundingRateRr', fundingRateEr),
            'fundingTimestamp': None,
            'fundingDatetime': None,
            'nextFundingRate': self.safe_number(contract, 'predFundingRateRr', nextFundingRateEr),
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
            'interval': None,
        }

    def set_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """
        Either adds or reduces margin in an isolated position in order to set the margin to a specific value

        https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#assign-position-balance-in-isolated-marign-mode

        :param str symbol: unified market symbol of the market to set margin in
        :param float amount: the amount to set the margin to
        :param dict [params]: parameters specific to the exchange API endpoint
        :returns dict: A `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            'posBalanceEv': self.to_ev(amount, market),
        }
        response = self.privatePostPositionsAssign(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "",
        #         "data": "OK"
        #     }
        #
        return self.extend(self.parse_margin_modification(response, market), {
            'amount': amount,
        })

    def parse_margin_status(self, status):
        statuses: dict = {
            '0': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
        #
        #     {
        #         "code": 0,
        #         "msg": "",
        #         "data": "OK"
        #     }
        #
        market = self.safe_market(None, market)
        inverse = self.safe_value(market, 'inverse')
        codeCurrency = 'base' if inverse else 'quote'
        return {
            'info': data,
            'symbol': self.safe_symbol(None, market),
            'type': 'set',
            'marginMode': 'isolated',
            'amount': None,
            'total': None,
            'code': market[codeCurrency],
            'status': self.parse_margin_status(self.safe_string(data, 'code')),
            'timestamp': None,
            'datetime': None,
        }

    def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
        """
        set margin mode to 'cross' or 'isolated'

        https://phemex-docs.github.io/#set-leverage

        :param str marginMode: 'cross' or 'isolated'
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from the exchange
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        if not market['swap'] or market['settle'] == 'USDT':
            raise BadSymbol(self.id + ' setMarginMode() supports swap(non USDT based) contracts only')
        marginMode = marginMode.lower()
        if marginMode != 'isolated' and marginMode != 'cross':
            raise BadRequest(self.id + ' setMarginMode() marginMode argument should be isolated or cross')
        leverage = self.safe_integer(params, 'leverage')
        if marginMode == 'cross':
            leverage = 0
        if leverage is None:
            raise ArgumentsRequired(self.id + ' setMarginMode() requires a leverage parameter')
        request: dict = {
            'symbol': market['id'],
            'leverage': leverage,
        }
        return self.privatePutPositionsLeverage(self.extend(request, params))

    def set_position_mode(self, hedged: bool, symbol: Str = None, params={}):
        """
        set hedged to True or False for a market

        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 str symbol: not used by binance setPositionMode()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: response from the exchange
        """
        self.check_required_argument('setPositionMode', symbol, 'symbol')
        self.load_markets()
        market = self.market(symbol)
        if market['settle'] != 'USDT':
            raise BadSymbol(self.id + ' setPositionMode() supports USDT settled markets only')
        request: dict = {
            'symbol': market['id'],
        }
        if hedged:
            request['targetPosMode'] = 'Hedged'
        else:
            request['targetPosMode'] = 'OneWay'
        return self.privatePutGPositionsSwitchPosModeSync(self.extend(request, params))

    def fetch_leverage_tiers(self, symbols: Strings = None, params={}) -> LeverageTiers:
        """
        retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
        :param str[]|None symbols: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `leverage tiers structures <https://docs.ccxt.com/#/?id=leverage-tiers-structure>`, indexed by market symbols
        """
        self.load_markets()
        if symbols is not None:
            first = self.safe_value(symbols, 0)
            market = self.market(first)
            if market['settle'] != 'USD':
                raise BadSymbol(self.id + ' fetchLeverageTiers() supports USD settled markets only')
        response = self.publicGetCfgV2Products(params)
        #
        #     {
        #         "code":0,
        #         "msg":"OK",
        #         "data":{
        #             "ratioScale":8,
        #             "currencies":[
        #                 {"currency":"BTC","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"Bitcoin"},
        #                 {"currency":"USD","valueScale":4,"minValueEv":1,"maxValueEv":500000000000000,"name":"USD"},
        #                 {"currency":"USDT","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"TetherUS"},
        #             ],
        #             "products":[
        #                 {
        #                     "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"
        #                 },
        #                 {
        #                     "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":[
        #                 {
        #                     "symbol":"BTCUSD",
        #                     "steps":"50",
        #                     "riskLimits":[
        #                         {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000},
        #                         {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000},
        #                         {"limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000},
        #                     ]
        #                 },
        #             ],
        #             "leverages":[
        #                 {"initialMargin":"1.0%","initialMarginEr":1000000,"options":[1,2,3,5,10,25,50,100]},
        #                 {"initialMargin":"1.5%","initialMarginEr":1500000,"options":[1,2,3,5,10,25,50,66]},
        #                 {"initialMargin":"2.0%","initialMarginEr":2000000,"options":[1,2,3,5,10,25,33,50]},
        #             ]
        #         }
        #     }
        #
        #
        data = self.safe_value(response, 'data', {})
        riskLimits = self.safe_list(data, 'riskLimits')
        return self.parse_leverage_tiers(riskLimits, symbols, 'symbol')

    def parse_market_leverage_tiers(self, info, market: Market = None) -> List[LeverageTier]:
        """
        :param dict info: Exchange market response for 1 market
        :param dict market: CCXT market
        """
        #
        #     {
        #         "symbol":"BTCUSD",
        #         "steps":"50",
        #         "riskLimits":[
        #             {"limit":100,"initialMargin":"1.0%","initialMarginEr":1000000,"maintenanceMargin":"0.5%","maintenanceMarginEr":500000},
        #             {"limit":150,"initialMargin":"1.5%","initialMarginEr":1500000,"maintenanceMargin":"1.0%","maintenanceMarginEr":1000000},
        #             {"limit":200,"initialMargin":"2.0%","initialMarginEr":2000000,"maintenanceMargin":"1.5%","maintenanceMarginEr":1500000},
        #         ]
        #     },
        #
        marketId = self.safe_string(info, 'symbol')
        market = self.safe_market(marketId, market)
        riskLimits = (market['info']['riskLimits'])
        tiers = []
        minNotional = 0
        for i in range(0, len(riskLimits)):
            tier = riskLimits[i]
            maxNotional = self.safe_integer(tier, 'limit')
            tiers.append({
                'tier': self.sum(i, 1),
                'symbol': self.safe_symbol(marketId, market),
                'currency': market['settle'],
                'minNotional': minNotional,
                'maxNotional': maxNotional,
                'maintenanceMarginRate': self.safe_string(tier, 'maintenanceMargin'),
                'maxLeverage': None,
                'info': tier,
            })
            minNotional = maxNotional
        return tiers

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        query = self.omit(params, self.extract_params(path))
        requestPath = '/' + self.implode_params(path, params)
        url = requestPath
        queryString = ''
        if (method == 'GET') or (method == 'DELETE') or (method == 'PUT') or (url == '/positions/assign'):
            if query:
                queryString = self.urlencode_with_array_repeat(query)
                url += '?' + queryString
        if api == 'private':
            self.check_required_credentials()
            timestamp = self.seconds()
            xPhemexRequestExpiry = self.safe_integer(self.options, 'x-phemex-request-expiry', 60)
            expiry = self.sum(timestamp, xPhemexRequestExpiry)
            expiryString = str(expiry)
            headers = {
                'x-phemex-access-token': self.apiKey,
                'x-phemex-request-expiry': expiryString,
            }
            payload = ''
            if method == 'POST':
                isOrderPlacement = (path == 'g-orders') or (path == 'spot/orders') or (path == 'orders')
                if isOrderPlacement:
                    if self.safe_string(params, 'clOrdID') is None:
                        id = self.safe_string(self.options, 'brokerId', 'CCXT123456')
                        params['clOrdID'] = id + self.uuid16()
                payload = self.json(params)
                body = payload
                headers['Content-Type'] = 'application/json'
            auth = requestPath + queryString + expiryString + payload
            headers['x-phemex-request-signature'] = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
        url = self.implode_hostname(self.urls['api'][api]) + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def set_leverage(self, leverage: Int, symbol: Str = None, params={}):
        """
        set the level of leverage for a market

        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 str symbol: unified market symbol
        :param dict [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
        :returns dict: 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 is None:
            raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
        if (leverage < -100) or (leverage > 100):
            raise BadRequest(self.id + ' setLeverage() leverage should be between -100 and 100')
        self.load_markets()
        isHedged = self.safe_bool(params, 'hedged', False)
        longLeverageRr = self.safe_integer(params, 'longLeverageRr')
        shortLeverageRr = self.safe_integer(params, 'shortLeverageRr')
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        response = None
        if market['settle'] == 'USDT':
            if not isHedged and longLeverageRr is None and shortLeverageRr is None:
                request['leverageRr'] = leverage
            else:
                longVar = longLeverageRr if (longLeverageRr is not None) else leverage
                shortVar = shortLeverageRr if (shortLeverageRr is not None) else leverage
                request['longLeverageRr'] = longVar
                request['shortLeverageRr'] = shortVar
            response = self.privatePutGPositionsLeverage(self.extend(request, params))
        else:
            request['leverage'] = leverage
            response = self.privatePutPositionsLeverage(self.extend(request, params))
        return response

    def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account

        https://phemex-docs.github.io/#transfer-between-spot-and-futures
        https://phemex-docs.github.io/#universal-transfer-main-account-only-transfer-between-sub-to-main-main-to-sub-or-sub-to-sub

        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.bizType]: for transferring between main and sub-acounts either 'SPOT' or 'PERPETUAL' default is 'SPOT'
        :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
        toId = self.safe_string(accountsByType, toAccount, toAccount)
        scaledAmmount = self.to_ev(amount, currency)
        direction = None
        transfer = None
        if fromId == 'spot' and toId == 'future':
            direction = 2
        elif fromId == 'future' and toId == 'spot':
            direction = 1
        if direction is not None:
            request: dict = {
                'currency': currency['id'],
                'moveOp': direction,
                'amountEv': scaledAmmount,
            }
            response = self.privatePostAssetsTransfer(self.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 = self.safe_value(response, 'data', {})
            transfer = self.parse_transfer(data, currency)
        else:  # sub account transfer
            request: dict = {
                'fromUserId': fromId,
                'toUserId': toId,
                'amountEv': scaledAmmount,
                'currency': currency['id'],
                'bizType': self.safe_string(params, 'bizType', 'SPOT'),
            }
            response = self.privatePostAssetsUniversalTransfer(self.extend(request, params))
            #
            #     {
            #         "code": "0",
            #         "msg": "OK",
            #         "data": "API-923db826-aaaa-aaaa-aaaa-4d98c3a7c9fd"
            #     }
            #
            transfer = self.parse_transfer(response)
        transferOptions = self.safe_value(self.options, 'transfer', {})
        fillResponseFromRequest = self.safe_bool(transferOptions, 'fillResponseFromRequest', True)
        if fillResponseFromRequest:
            if transfer['fromAccount'] is None:
                transfer['fromAccount'] = fromAccount
            if transfer['toAccount'] is None:
                transfer['toAccount'] = toAccount
            if transfer['amount'] is None:
                transfer['amount'] = amount
            if transfer['currency'] is None:
                transfer['currency'] = code
        return transfer

    def fetch_transfers(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[TransferEntry]:
        """
        fetch a history of internal transfers made on an account

        https://phemex-docs.github.io/#query-transfer-history

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transfer structures <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        self.load_markets()
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchTransfers() requires a code argument')
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        if since is not None:
            request['start'] = since
        if limit is not None:
            request['limit'] = limit
        response = self.privateGetAssetsTransfer(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "OK",
        #         "data": {
        #             "rows": [
        #                 {
        #                     "linkKey": "87c071a3-8628-4ac2-aca1-6ce0d1fad66c",
        #                     "userId": 4148428,
        #                     "currency": "BTC",
        #                     "amountEv": 67932,
        #                     "side": 2,
        #                     "status": 10,
        #                     "createTime": 1652832467000,
        #                     "bizType": 10
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        transfers = self.safe_list(data, 'rows', [])
        return self.parse_transfers(transfers, currency, since, limit)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        # 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 = self.safe_string(transfer, 'linkKey')
        status = self.safe_string(transfer, 'status')
        amountEv = self.safe_string(transfer, 'amountEv')
        amountTransfered = self.from_ev(amountEv)
        currencyId = self.safe_string(transfer, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        side = self.safe_integer(transfer, 'side')
        fromId = None
        toId = None
        if side == 1:
            fromId = 'swap'
            toId = 'spot'
        elif side == 2:
            fromId = 'spot'
            toId = 'swap'
        timestamp = self.safe_integer(transfer, 'createTime')
        return {
            'info': transfer,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'currency': code,
            'amount': amountTransfered,
            'fromAccount': fromId,
            'toAccount': toId,
            'status': self.parse_transfer_status(status),
        }

    def parse_transfer_status(self, status: Str) -> Str:
        statuses: dict = {
            '3': 'rejected',  # 'Rejected',
            '6': 'canceled',  # 'Got error and wait for recovery',
            '10': 'ok',  # 'Success',
            '11': 'failed',  # 'Failed',
        }
        return self.safe_string(statuses, status, status)

    def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetches historical funding rate prices

        https://phemex-docs.github.io/#query-funding-rate-history-2

        :param str 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 `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self 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
        :returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        isUsdtSettled = market['settle'] == 'USDT'
        if not market['swap']:
            raise BadRequest(self.id + ' fetchFundingRateHistory() supports swap contracts only')
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate')
        if paginate:
            return self.fetch_paginated_call_deterministic('fetchFundingRateHistory', symbol, since, limit, '8h', params, 100)
        customSymbol = None
        if isUsdtSettled:
            customSymbol = '.' + market['id'] + 'FR8H'  # phemex requires a custom symbol for funding rate history
        else:
            customSymbol = '.' + market['baseId'] + 'FR8H'
        request: dict = {
            'symbol': customSymbol,
        }
        if since is not None:
            request['start'] = since
        if limit is not None:
            request['limit'] = limit
        request, params = self.handle_until_option('end', request, params)
        response = None
        if isUsdtSettled:
            response = self.v2GetApiDataPublicDataFundingRateHistory(self.extend(request, params))
        else:
            response = self.v1GetApiDataPublicDataFundingRateHistory(self.extend(request, params))
        #
        #    {
        #        "code":"0",
        #        "msg":"OK",
        #        "data":{
        #           "rows":[
        #              {
        #                 "symbol":".BTCUSDTFR8H",
        #                 "fundingRate":"0.0001",
        #                 "fundingTime":"1682064000000",
        #                 "intervalSeconds":"28800"
        #              }
        #           ]
        #        }
        #    }
        #
        data = self.safe_value(response, 'data', {})
        rates = self.safe_value(data, 'rows')
        result = []
        for i in range(0, len(rates)):
            item = rates[i]
            timestamp = self.safe_integer(item, 'fundingTime')
            result.append({
                'info': item,
                'symbol': symbol,
                'fundingRate': self.safe_number(item, 'fundingRate'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
            })
        sorted = self.sort_by(result, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)

    def withdraw(self, code: str, amount: float, address: str, tag=None, params={}) -> Transaction:
        """
        make a withdrawal

        https://phemex-docs.github.io/#create-withdraw-request

        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the phemex api endpoint
        :param str [params.network]: unified network code
        :returns dict: a `transaction structure <https://github.com/ccxt/ccxt/wiki/Manual#transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.load_markets()
        self.check_address(address)
        currency = self.currency(code)
        networkCode = None
        networkCode, params = self.handle_network_code_and_params(params)
        networkId = None
        if networkCode is not None:
            networkId = self.network_code_to_id(networkCode)
        stableCoins = self.safe_value(self.options, 'stableCoins')
        if networkId is None:
            if not (self.in_array(code, stableCoins)):
                networkId = currency['id']
            else:
                raise ArgumentsRequired(self.id + ' withdraw() requires an extra argument params["network"]')
        request: dict = {
            'currency': currency['id'],
            'address': address,
            'amount': amount,
            'chainName': networkId.upper(),
        }
        if tag is not None:
            request['addressTag'] = tag
        response = self.privatePostPhemexWithdrawWalletsApiCreateWithdraw(self.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 = self.safe_dict(response, 'data', {})
        return self.parse_transaction(data, currency)

    def fetch_open_interest(self, symbol: str, params={}):
        """
        retrieves the open interest of a trading pair

        https://phemex-docs.github.io/#query-24-hours-ticker

        :param str symbol: unified CCXT market symbol
        :param dict [params]: exchange specific parameters
        :returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure:
        """
        self.load_markets()
        market = self.market(symbol)
        if not market['contract']:
            raise BadRequest(self.id + ' fetchOpenInterest is only supported for contract markets.')
        request: dict = {
            'symbol': market['id'],
        }
        response = self.v2GetMdV2Ticker24hr(self.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 = self.safe_dict(response, 'result')
        return self.parse_open_interest(result, market)

    def parse_open_interest(self, interest, market: Market = None):
        #
        #    {
        #        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 = self.safe_integer(interest, 'timestamp') / 1000000
        id = self.safe_string(interest, 'symbol')
        return self.safe_open_interest({
            'info': interest,
            'symbol': self.safe_symbol(id, market),
            'baseVolume': self.safe_string(interest, 'volumeRq'),
            'quoteVolume': None,  # deprecated
            'openInterestAmount': self.safe_string(interest, 'openInterestRv'),
            'openInterestValue': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
        }, market)

    def fetch_convert_quote(self, fromCode: str, toCode: str, amount: Num = None, params={}) -> Conversion:
        """
        fetch a quote for converting from one currency to another

        https://phemex-docs.github.io/#rfq-quote

        :param str fromCode: the currency that you want to sell and convert from
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
        """
        self.load_markets()
        fromCurrency = self.currency(fromCode)
        toCurrency = self.currency(toCode)
        valueScale = self.safe_integer(fromCurrency, 'valueScale')
        request: dict = {
            'fromCurrency': fromCode,
            'toCurrency': toCode,
            'fromAmountEv': self.to_en(amount, valueScale),
        }
        response = self.privateGetAssetsQuote(self.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 = self.safe_dict(response, 'data', {})
        return self.parse_conversion(data, fromCurrency, toCurrency)

    def create_convert_trade(self, id: str, fromCode: str, toCode: str, amount: Num = None, params={}) -> Conversion:
        """
        convert from one currency to another

        https://phemex-docs.github.io/#convert

        :param str id: the id of the trade that you want to make
        :param str fromCode: the currency that you want to sell and convert from
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `conversion structure <https://docs.ccxt.com/#/?id=conversion-structure>`
        """
        self.load_markets()
        fromCurrency = self.currency(fromCode)
        toCurrency = self.currency(toCode)
        valueScale = self.safe_integer(fromCurrency, 'valueScale')
        request: dict = {
            'code': id,
            'fromCurrency': fromCode,
            'toCurrency': toCode,
        }
        if amount is not None:
            request['fromAmountEv'] = self.to_en(amount, valueScale)
        response = self.privatePostAssetsConvert(self.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 = self.safe_dict(response, 'data', {})
        fromCurrencyId = self.safe_string(data, 'fromCurrency')
        fromResult = self.safe_currency(fromCurrencyId, fromCurrency)
        toCurrencyId = self.safe_string(data, 'toCurrency')
        to = self.safe_currency(toCurrencyId, toCurrency)
        return self.parse_conversion(data, fromResult, to)

    def fetch_convert_trade_history(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Conversion]:
        """
        fetch the users history of conversion trades

        https://phemex-docs.github.io/#query-convert-history

        :param str [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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.until]: the end time in ms
        :param str [params.fromCurrency]: the currency that you sold and converted from
        :param str [params.toCurrency]: the currency that you bought and converted into
        :returns dict[]: a list of `conversion structures <https://docs.ccxt.com/#/?id=conversion-structure>`
        """
        self.load_markets()
        request: dict = {}
        if code is not None:
            request['fromCurrency'] = code
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        request, params = self.handle_until_option('endTime', request, params)
        response = self.privateGetAssetsConvert(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "OK",
        #         "data": {
        #             "total": 2,
        #             "rows": [
        #                 {
        #                     "linkKey": "45c8ed8e-d3f4-472d-8262-e464e8c46247",
        #                     "createTime": 1739882294000,
        #                     "fromCurrency": "USDT",
        #                     "toCurrency": "BTC",
        #                     "fromAmountEv": 4000000000,
        #                     "toAmountEv": 41511,
        #                     "status": 10,
        #                     "conversionRate": 1037,
        #                     "errorCode": 0
        #                 },
        #             ]
        #         }
        #     }
        #
        data = self.safe_dict(response, 'data', {})
        rows = self.safe_list(data, 'rows', [])
        return self.parse_conversions(rows, code, 'fromCurrency', 'toCurrency', since, limit)

    def parse_conversion(self, conversion: dict, fromCurrency: Currency = None, toCurrency: Currency = None) -> Conversion:
        #
        # 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 = self.safe_dict(conversion, 'quoteArgs', {})
        requestTime = self.safe_integer(quoteArgs, 'requestAt')
        timestamp = self.safe_integer(conversion, 'createTime', requestTime)
        fromCoin = self.safe_string(conversion, 'fromCurrency', self.safe_string(fromCurrency, 'code'))
        fromCode = self.safe_currency_code(fromCoin, fromCurrency)
        toCoin = self.safe_string(conversion, 'toCurrency', self.safe_string(toCurrency, 'code'))
        toCode = self.safe_currency_code(toCoin, toCurrency)
        fromValueScale = self.safe_integer(fromCurrency, 'valueScale')
        toValueScale = self.safe_integer(toCurrency, 'valueScale')
        fromAmount = self.from_en(self.safe_string(conversion, 'fromAmountEv'), fromValueScale)
        if fromAmount is None and quoteArgs is not None:
            fromAmount = self.from_en(self.safe_string(quoteArgs, 'origin'), fromValueScale)
        toAmount = self.from_en(self.safe_string(conversion, 'toAmountEv'), toValueScale)
        if toAmount is None and quoteArgs is not None:
            toAmount = self.from_en(self.safe_string(quoteArgs, 'proceeds'), toValueScale)
        return {
            'info': conversion,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'id': self.safe_string(conversion, 'code'),
            'fromCurrency': fromCode,
            'fromAmount': self.parse_number(fromAmount),
            'toCurrency': toCode,
            'toAmount': self.parse_number(toAmount),
            'price': self.safe_number(quoteArgs, 'price'),
            'fee': None,
        }

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None  # fallback to default error handler
        #
        #     {"code":30018,"msg":"phemex.data.size.uplimt","data":null}
        #     {"code":412,"msg":"Missing parameter - resolution","data":null}
        #     {"code":412,"msg":"Missing parameter - to","data":null}
        #     {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null}
        #
        error = self.safe_value(response, 'error', response)
        errorCode = self.safe_string(error, 'code')
        message = self.safe_string(error, 'msg')
        if (errorCode is not None) and (errorCode != '0'):
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
        return None
