5032 lines
240 KiB
Python
5032 lines
240 KiB
Python
# -*- 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.async_support.base.exchange import Exchange
|
|
from ccxt.abstract.phemex import ImplicitAPI
|
|
import asyncio
|
|
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>¤cy=<currency>&ordStatus=<ordStatus>&ordType=<ordType>&start=<start>&end=<end>&offset=<offset>&limit=<limit>&withCount=<withCount></withCount>
|
|
'exchange/order/v2/tradingList': 5, # ?symbol=<symbol>¤cy=<currency>&execType=<execType>&offset=<offset>&limit=<limit>&withCount=<withCount>
|
|
# swap
|
|
'accounts/accountPositions': 1, # ?currency=<currency>
|
|
'g-accounts/accountPositions': 1, # ?currency=<currency>
|
|
'g-accounts/positions': 25, # ?currency=<currency>
|
|
'g-accounts/risk-unit': 1,
|
|
'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¤cy=USDT
|
|
'phemex-user/wallets/tradeAccountDetail': 5, # ?bizCode=¤cy=&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>"eQtyEv=<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>"eQtyEv=<quoteQtyEv>&stopPxEp=<stopPxEp>
|
|
# swap
|
|
'orders/replace': 1, # ?symbol=<symbol>&orderID=<orderID>&origClOrdID=<origClOrdID>&clOrdID=<clOrdID>&price=<price>&priceEp=<priceEp>&orderQty=<orderQty>&stopPx=<stopPx>&stopPxEp=<stopPxEp>&takeProfit=<takeProfit>&takeProfitEp=<takeProfitEp>&stopLoss=<stopLoss>&stopLossEp=<stopLossEp>&pegOffsetValueEp=<pegOffsetValueEp>&pegPriceType=<pegPriceType>
|
|
'g-orders/replace': 1, # ?symbol=<symbol>&orderID=<orderID>&origClOrdID=<origClOrdID>&clOrdID=<clOrdID>&price=<price>&priceEp=<priceEp>&orderQty=<orderQty>&stopPx=<stopPx>&stopPxEp=<stopPxEp>&takeProfit=<takeProfit>&takeProfitEp=<takeProfitEp>&stopLoss=<stopLoss>&stopLossEp=<stopLossEp>&pegOffsetValueEp=<pegOffsetValueEp>&pegPriceType=<pegPriceType>
|
|
'g-orders/create': 1,
|
|
'positions/leverage': 5, # ?symbol=<symbol>&leverage=<leverage>&leverageEr=<leverageEr>
|
|
'g-positions/leverage': 5, # ?symbol=<symbol>&leverage=<leverage>&leverageEr=<leverageEr>
|
|
'g-positions/switch-pos-mode-sync': 5, # ?symbol=<symbol>&targetPosMode=<targetPosMode>
|
|
'positions/riskLimit': 5, # ?symbol=<symbol>&riskLimit=<riskLimit>&riskLimitEv=<riskLimitEv>
|
|
},
|
|
'delete': {
|
|
# 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',
|
|
'MKR': '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,
|
|
})
|
|
|
|
async 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 = await asyncio.gather(*[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
|
|
|
|
async 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 = await 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')
|
|
code = self.safe_currency_code(id)
|
|
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] = self.safe_currency_structure({
|
|
'id': id,
|
|
'info': currency,
|
|
'code': code,
|
|
'name': self.safe_string(currency, 'name'),
|
|
'active': self.safe_string(currency, '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
|
|
|
|
async 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
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
# 'id': 123456789, # optional request id
|
|
}
|
|
response = None
|
|
isStableSettled = (market['settle'] == 'USDT') or (market['settle'] == 'USDC')
|
|
if market['linear'] and isStableSettled:
|
|
response = await self.v2GetMdV2Orderbook(self.extend(request, params))
|
|
else:
|
|
if (limit is not None) and (limit <= 30):
|
|
response = await self.v1GetMdOrderbook(self.extend(request, params))
|
|
else:
|
|
response = await 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: dict = 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,
|
|
]
|
|
|
|
async def fetch_ohlcv(self, symbol: str, timeframe: str = '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
|
|
"""
|
|
await 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'])
|
|
isStableSettled = (market['settle'] == 'USDT') or (market['settle'] == 'USDC')
|
|
usesSpecialFromToEndpoint = ((market['linear'] or isStableSettled)) 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 isStableSettled:
|
|
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 = await self.publicGetMdV2KlineList(self.extend(request, params))
|
|
else:
|
|
response = await 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 = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await self.v1GetMdTicker24hr(self.extend(request, params))
|
|
else:
|
|
response = await self.v2GetMdV2Ticker24hr(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await self.v1GetMdSpotTicker24hrAll(query)
|
|
elif subType == 'inverse' or self.safe_string(market, 'settle') == 'USD':
|
|
response = await self.v1GetMdTicker24hrAll(query)
|
|
else:
|
|
response = await self.v2GetMdV2Ticker24hrAll(query)
|
|
result = self.safe_list(response, 'result', [])
|
|
return self.parse_tickers(result, symbols)
|
|
|
|
async 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>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
# 'id': 123456789, # optional request id
|
|
}
|
|
response = None
|
|
isStableSettled = (market['settle'] == 'USDT') or (market['settle'] == 'USDC')
|
|
if market['linear'] and isStableSettled:
|
|
response = await self.v2GetMdV2Trade(self.extend(request, params))
|
|
else:
|
|
response = await 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' or market['settle'] == 'USDC':
|
|
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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await self.privateGetGAccountsAccountPositions(self.extend(request, params))
|
|
else:
|
|
response = await 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 = await self.privateGetSpotWallets(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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)
|
|
isStableSettled = (market['settle'] == 'USDT') or (market['settle'] == 'USDC')
|
|
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 isStableSettled:
|
|
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 isStableSettled:
|
|
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 'ascending' or 'descending' value")
|
|
# the flow defined per https://phemex-docs.github.io/#more-order-type-examples
|
|
if triggerDirection == 'ascending' or 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 == 'descending' or 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 isStableSettled:
|
|
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 isStableSettled:
|
|
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 isStableSettled:
|
|
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 isStableSettled:
|
|
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 isStableSettled:
|
|
request['stopLossRp'] = self.price_to_precision(symbol, stopLossPrice)
|
|
else:
|
|
request['stopLossEp'] = self.to_ep(stopLossPrice, market)
|
|
params = self.omit(params, 'stopLossPrice')
|
|
response = None
|
|
if isStableSettled:
|
|
response = await self.privatePostGOrders(self.extend(request, params))
|
|
elif market['contract']:
|
|
response = await self.privatePostOrders(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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'])
|
|
isStableSettled = (market['settle'] == 'USDT') or (market['settle'] == 'USDC')
|
|
if clientOrderId is not None:
|
|
request['clOrdID'] = clientOrderId
|
|
else:
|
|
request['orderID'] = id
|
|
if price is not None:
|
|
if isStableSettled:
|
|
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 isStableSettled:
|
|
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 isStableSettled:
|
|
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 isStableSettled:
|
|
posSide = self.safe_string(params, 'posSide')
|
|
if posSide is None:
|
|
request['posSide'] = 'Merged'
|
|
response = await self.privatePutGOrdersReplace(self.extend(request, params))
|
|
elif market['swap']:
|
|
response = await self.privatePutOrdersReplace(self.extend(request, params))
|
|
else:
|
|
response = await self.privatePutSpotOrders(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_order(data, market)
|
|
|
|
async 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')
|
|
await 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' or market['settle'] == 'USDC':
|
|
posSide = self.safe_string(params, 'posSide')
|
|
if posSide is None:
|
|
request['posSide'] = 'Merged'
|
|
response = await self.privateDeleteGOrdersCancel(self.extend(request, params))
|
|
elif market['swap']:
|
|
response = await self.privateDeleteOrdersCancel(self.extend(request, params))
|
|
else:
|
|
response = await self.privateDeleteSpotOrders(self.extend(request, params))
|
|
data = self.safe_dict(response, 'data', {})
|
|
return self.parse_order(data, market)
|
|
|
|
async 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')
|
|
await 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' or market['settle'] == 'USDC':
|
|
response = await self.privateDeleteGOrdersAll(self.extend(request, params))
|
|
#
|
|
# {
|
|
# code: '0',
|
|
# msg: '',
|
|
# data: '1'
|
|
# }
|
|
#
|
|
elif market['swap']:
|
|
response = await self.privateDeleteOrdersAll(self.extend(request, params))
|
|
#
|
|
# {
|
|
# code: '0',
|
|
# msg: '',
|
|
# data: '1'
|
|
# }
|
|
#
|
|
else:
|
|
response = await self.privateDeleteSpotOrdersAll(self.extend(request, params))
|
|
#
|
|
# {
|
|
# code: '0',
|
|
# msg: '',
|
|
# data: {
|
|
# total: '1'
|
|
# }
|
|
# }
|
|
#
|
|
return [
|
|
self.safe_order({
|
|
'info': response,
|
|
}),
|
|
]
|
|
|
|
async 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')
|
|
await 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' or market['settle'] == 'USDC':
|
|
response = await self.privateGetApiDataGFuturesOrdersByOrderId(self.extend(request, params))
|
|
elif market['spot']:
|
|
response = await self.privateGetApiDataSpotsOrdersByOrderId(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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')
|
|
await 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' or market['settle'] == 'USDC':
|
|
request['currency'] = market['settle']
|
|
response = await self.privateGetExchangeOrderV2OrderList(self.extend(request, params))
|
|
elif market['swap']:
|
|
response = await self.privateGetExchangeOrderList(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await self.load_markets()
|
|
if symbol is None:
|
|
raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
response = None
|
|
try:
|
|
if market['settle'] == 'USDT' or market['settle'] == 'USDC':
|
|
response = await self.privateGetGOrdersActiveList(self.extend(request, params))
|
|
elif market['swap']:
|
|
response = await self.privateGetOrdersActiveList(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await self.privateGetExchangeOrderV2OrderList(self.extend(request, params))
|
|
elif market['swap']:
|
|
response = await self.privateGetExchangeOrderList(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await self.load_markets()
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
type = None
|
|
type, params = self.handle_market_type_and_params('fetchMyTrades', market, params)
|
|
request: dict = {}
|
|
if limit is not None:
|
|
limit = min(200, limit)
|
|
request['limit'] = limit
|
|
isUSDTSettled = (type != 'spot') and ((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
|
|
elif symbol is not None:
|
|
request['symbol'] = market['id']
|
|
if since is not None:
|
|
request['start'] = since
|
|
response = None
|
|
if isUSDTSettled:
|
|
response = await self.privateGetExchangeOrderV2TradingList(self.extend(request, params))
|
|
elif type == 'swap':
|
|
request['tradeType'] = 'Trade'
|
|
response = await self.privateGetExchangeOrderTrade(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async 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
|
|
:param str [params.network]: the chain name to fetch the deposit address e.g. ETH, TRX, EOS, SOL, etc.
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
await 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_2(params, 'network', 'chainName', defaultNetwork)
|
|
network = self.safe_string(networks, network, network)
|
|
if network is None:
|
|
raise ArgumentsRequired(self.id + ' fetchDepositAddress() requires a network parameter')
|
|
else:
|
|
request['chainName'] = network
|
|
params = self.omit(params, 'network')
|
|
response = await self.privateGetExchangeWalletsV2DepositAddress(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 0,
|
|
# "msg": "OK",
|
|
# "data": {
|
|
# "address": "tb1qxel5wq5gumt",
|
|
# "tag": "",
|
|
# "notice": False,
|
|
# "accountType": 1,
|
|
# "contractName": null,
|
|
# "chainTokenUrl": null,
|
|
# "sign": 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,
|
|
}
|
|
|
|
async 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>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
response = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
response = await 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,
|
|
}
|
|
|
|
async 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 'privateGetGAccountsAccountPositions' default is 'privateGetGAccountsAccountPositions'
|
|
:returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
|
|
"""
|
|
await 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 = await self.privateGetGAccountsAccountPositions(self.extend(request, params))
|
|
else:
|
|
response = await self.privateGetGAccountsPositions(self.extend(request, params))
|
|
else:
|
|
response = await 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_2(position, 'size', 'sizeRq')
|
|
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'
|
|
# Inverse long contract: unRealizedPnl = (posSize * contractSize) / avgEntryPrice - (posSize * contractSize) / markPrice
|
|
# Inverse short contract: unRealizedPnl = (posSize *contractSize) / markPrice - (posSize * contractSize) / avgEntryPrice
|
|
# Linear long contract: unRealizedPnl = (posSize * contractSize) * markPrice - (posSize * contractSize) * avgEntryPrice
|
|
# Linear short contract: unRealizedPnl = (posSize * contractSize) * avgEntryPrice - (posSize * contractSize) * markPrice
|
|
priceDiff = None
|
|
if market['linear']:
|
|
if side == 'long':
|
|
priceDiff = Precise.string_sub(markPriceString, entryPriceString)
|
|
else:
|
|
priceDiff = Precise.string_sub(entryPriceString, markPriceString)
|
|
else:
|
|
# inverse
|
|
if side == 'long':
|
|
priceDiff = Precise.string_sub(Precise.string_div('1', entryPriceString), Precise.string_div('1', markPriceString))
|
|
else:
|
|
priceDiff = Precise.string_sub(Precise.string_div('1', markPriceString), Precise.string_div('1', entryPriceString))
|
|
unrealizedPnl = Precise.string_mul(Precise.string_mul(priceDiff, contracts), contractSizeString)
|
|
# the unrealizedPnl is only available in a specific endpoint which much higher RL limits
|
|
apiUnrealizedPnl = self.safe_string(position, 'unRealisedPnlRv', unrealizedPnl)
|
|
marginRatio = Precise.string_div(maintenanceMarginString, collateral)
|
|
isCross = self.safe_value(position, 'crossMargin')
|
|
return self.safe_position({
|
|
'info': position,
|
|
'id': self.safe_string(position, 'execSeq'),
|
|
'symbol': symbol,
|
|
'contracts': self.parse_number(contracts),
|
|
'contractSize': contractSize,
|
|
'realizedPnl': self.safe_number(position, 'curTermRealisedPnlRv'),
|
|
'unrealizedPnl': self.parse_number(apiUnrealizedPnl),
|
|
'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': self.safe_integer_product(position, 'transactTimeNs', 0.000001),
|
|
'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': self.safe_string(position, 'posMode') == 'Hedged',
|
|
'percentage': None,
|
|
'stopLossPrice': None,
|
|
'takeProfitPrice': None,
|
|
})
|
|
|
|
async 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')
|
|
await 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
|
|
isStableSettled = market['settle'] == 'USDT' or market['settle'] == 'USDC'
|
|
if isStableSettled:
|
|
response = await self.privateGetApiDataGFuturesFundingFees(self.extend(request, params))
|
|
else:
|
|
response = await 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'
|
|
isStableSettled = market['settle'] == 'USDT' or market['settle'] == 'USDC'
|
|
if not isStableSettled:
|
|
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
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await self.v1GetMdTicker24hr(self.extend(request, params))
|
|
else:
|
|
response = await 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,
|
|
}
|
|
|
|
async 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>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
'posBalanceEv': self.to_ev(amount, market),
|
|
}
|
|
response = await 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,
|
|
}
|
|
|
|
async 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')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
if not market['swap']:
|
|
raise BadSymbol(self.id + ' setMarginMode() supports swap contracts only')
|
|
marginMode = marginMode.lower()
|
|
if marginMode != 'isolated' and marginMode != 'cross':
|
|
raise BadRequest(self.id + ' setMarginMode() marginMode argument should be isolated or cross')
|
|
request: dict = {
|
|
'symbol': market['id'],
|
|
}
|
|
isCross = marginMode == 'cross'
|
|
if self.in_array(market['settle'], ['USDT', 'USDC']):
|
|
currentLeverage = self.safe_string(params, 'leverage')
|
|
if currentLeverage is None:
|
|
raise ArgumentsRequired(self.id + ' setMarginMode() requires a "leverage" parameter for USDT markets')
|
|
request['leverageRr'] = Precise.string_neg(Precise.string_abs(currentLeverage)) if isCross else Precise.string_abs(currentLeverage)
|
|
return await self.privatePutGPositionsLeverage(self.extend(request, params))
|
|
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['leverage'] = leverage
|
|
return await self.privatePutPositionsLeverage(self.extend(request, params))
|
|
|
|
async 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')
|
|
await 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 await self.privatePutGPositionsSwitchPosModeSync(self.extend(request, params))
|
|
|
|
async 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
|
|
"""
|
|
await 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 = await 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}
|
|
|
|
async 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')
|
|
await 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' or market['settle'] == 'USDC':
|
|
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 = await self.privatePutGPositionsLeverage(self.extend(request, params))
|
|
else:
|
|
request['leverage'] = leverage
|
|
response = await self.privatePutPositionsLeverage(self.extend(request, params))
|
|
return response
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await 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 = await 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
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await 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)
|
|
|
|
async 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')
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
isUsdtSettled = market['settle'] == 'USDT' or market['settle'] == 'USDC'
|
|
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 await 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 = await self.v2GetApiDataPublicDataFundingRateHistory(self.extend(request, params))
|
|
else:
|
|
response = await 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)
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = 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)
|
|
await 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 = await 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)
|
|
|
|
async 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:
|
|
"""
|
|
await 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 = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await 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)
|
|
|
|
async 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>`
|
|
"""
|
|
await 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 = await 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
|