@proton/ccxt
Version:
A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 130+ exchanges
1,112 lines (1,110 loc) • 195 kB
JavaScript
// ----------------------------------------------------------------------------
// 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
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
// ----------------------------------------------------------------------------
import Exchange from './abstract/phemex.js';
import { ExchangeError, BadSymbol, AuthenticationError, InsufficientFunds, InvalidOrder, ArgumentsRequired, OrderNotFound, BadRequest, PermissionDenied, AccountSuspended, CancelPending, DDoSProtection, DuplicateOrderId, RateLimitExceeded, NotSupported } from './base/errors.js';
import { Precise } from './base/Precise.js';
import { TICK_SIZE } from './base/functions/number.js';
import { sha256 } from './static_dependencies/noble-hashes/sha256.js';
// ----------------------------------------------------------------------------
export default class phemex extends Exchange {
describe() {
return this.deepExtend(super.describe(), {
'id': 'phemex',
'name': 'Phemex',
'countries': ['CN'],
'rateLimit': 120.5,
'version': 'v1',
'certified': false,
'pro': true,
'hostname': 'api.phemex.com',
'has': {
'CORS': undefined,
'spot': true,
'margin': false,
'swap': true,
'future': false,
'option': false,
'addMargin': false,
'cancelAllOrders': true,
'cancelOrder': true,
'createOrder': true,
'createReduceOnlyOrder': true,
'createStopLimitOrder': true,
'createStopMarketOrder': true,
'createStopOrder': true,
'editOrder': true,
'fetchBalance': true,
'fetchBorrowRate': false,
'fetchBorrowRateHistories': false,
'fetchBorrowRateHistory': false,
'fetchBorrowRates': false,
'fetchBorrowRatesPerSymbol': false,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchFundingHistory': true,
'fetchFundingRate': true,
'fetchFundingRateHistories': false,
'fetchFundingRateHistory': true,
'fetchFundingRates': false,
'fetchIndexOHLCV': false,
'fetchLeverage': false,
'fetchLeverageTiers': true,
'fetchMarketLeverageTiers': 'emulated',
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMyTrades': true,
'fetchOHLCV': 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,
'setLeverage': true,
'setMargin': true,
'setMarginMode': true,
'setPositionMode': true,
'transfer': true,
'withdraw': undefined,
},
'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://github.com/phemex/phemex-api-docs',
'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,
'cfg/fundingRates': 5,
'products': 5,
'nomics/trades': 5,
'md/kline': 5,
'md/v2/kline/list': 5,
'md/v2/kline': 5,
'md/v2/kline/last': 5, // perpetual ?symbol=<symbol>&resolution=<resolution>&limit=<limit>
},
},
'v1': {
'get': {
'md/orderbook': 5,
'md/trade': 5,
'md/ticker/24hr': 5,
'md/ticker/24hr/all': 5,
'md/spot/ticker/24hr': 5,
'md/spot/ticker/24hr/all': 5,
'exchange/public/products': 5,
'api-data/public/data/funding-rate-history': 5,
},
},
'v2': {
'get': {
'md/v2/orderbook': 5,
'md/v2/trade': 5,
'md/v2/ticker/24hr': 5,
'md/v2/ticker/24hr/all': 5,
'api-data/public/data/funding-rate-history': 5,
},
},
'private': {
'get': {
// spot
'spot/orders/active': 1,
// 'spot/orders/active': 5, // ?symbol=<symbol>&clOrDID=<clOrdID>
'spot/orders': 1,
'spot/wallets': 5,
'exchange/spot/order': 5,
'exchange/spot/order/trades': 5,
'exchange/order/v2/orderList': 5,
'exchange/order/v2/tradingList': 5,
// swap
'accounts/accountPositions': 1,
'g-accounts/accountPositions': 1,
'accounts/positions': 25,
'api-data/futures/funding-fees': 5,
'api-data/g-futures/funding-fees': 5,
'api-data/futures/orders': 5,
'api-data/g-futures/orders': 5,
'api-data/futures/orders/by-order-id': 5,
'api-data/g-futures/orders/by-order-id': 5,
'api-data/futures/trades': 5,
'api-data/g-futures/trades': 5,
'api-data/futures/trading-fees': 5,
'api-data/g-futures/trading-fees': 5,
'g-orders/activeList': 1,
'orders/activeList': 1,
'exchange/order/list': 5,
'exchange/order': 5,
// 'exchange/order': 5, // ?symbol=<symbol>&clOrdID=<clOrdID5,clOrdID2>
'exchange/order/trade': 5,
'phemex-user/users/children': 5,
'phemex-user/wallets/v2/depositAddress': 5,
'phemex-user/wallets/tradeAccountDetail': 5,
'phemex-user/order/closedPositionList': 5,
'exchange/margins/transfer': 5,
'exchange/wallets/confirm/withdraw': 5,
'exchange/wallets/withdrawList': 5,
'exchange/wallets/depositList': 5,
'exchange/wallets/v2/depositAddress': 5,
'api-data/spots/funds': 5,
'assets/convert': 5,
// transfer
'assets/transfer': 5,
'assets/spots/sub-accounts/transfer': 5,
'assets/futures/sub-accounts/transfer': 5,
'assets/quote': 5, // ?fromCurrency=<currency>&toCurrency=<currency>&amountEv=<amount>
},
'post': {
// spot
'spot/orders': 1,
// swap
'orders': 1,
'g-orders': 1,
'positions/assign': 5,
'exchange/wallets/transferOut': 5,
'exchange/wallets/transferIn': 5,
'exchange/margins': 5,
'exchange/wallets/createWithdraw': 5,
'exchange/wallets/cancelWithdraw': 5,
'exchange/wallets/createWithdrawAddress': 5,
// transfer
'assets/transfer': 5,
'assets/spots/sub-accounts/transfer': 5,
'assets/futures/sub-accounts/transfer': 5,
'assets/universal-transfer': 5,
'assets/convert': 5,
},
'put': {
// spot
'spot/orders': 1,
// swap
'orders/replace': 1,
'g-orders/replace': 1,
'positions/leverage': 5,
'g-positions/leverage': 5,
'g-positions/switch-pos-mode-sync': 5,
'positions/riskLimit': 5, // ?symbol=<symbol>&riskLimit=<riskLimit>&riskLimitEv=<riskLimitEv>
},
'delete': {
// spot
'spot/orders': 2,
'spot/orders/all': 2,
// 'spot/orders': 5, // ?symbol=<symbol>&clOrdID=<clOrdID>
// swap
'orders/cancel': 1,
'orders': 1,
'orders/all': 3,
'g-orders/cancel': 1,
'g-orders': 1,
'g-orders/all': 3, // ?symbol=<symbol>&untriggered=<untriggered>&text=<text>
},
},
},
'precisionMode': TICK_SIZE,
'fees': {
'trading': {
'tierBased': false,
'percentage': true,
'taker': this.parseNumber('0.001'),
'maker': this.parseNumber('0.001'),
},
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
},
'exceptions': {
'exact': {
// not documented
'412': BadRequest,
'6001': BadRequest,
// documented
'19999': BadRequest,
'10001': DuplicateOrderId,
'10002': OrderNotFound,
'10003': CancelPending,
'10004': CancelPending,
'10005': CancelPending,
'11001': InsufficientFunds,
'11002': InvalidOrder,
'11003': InsufficientFunds,
'11004': InvalidOrder,
'11005': InsufficientFunds,
'11006': ExchangeError,
'11007': ExchangeError,
'11008': ExchangeError,
'11009': ExchangeError,
'11010': InsufficientFunds,
'11011': InvalidOrder,
'11012': InvalidOrder,
'11013': InvalidOrder,
'11014': InvalidOrder,
'11015': InvalidOrder,
'11016': BadRequest,
'11017': ExchangeError,
'11018': ExchangeError,
'11019': ExchangeError,
'11020': ExchangeError,
'11021': ExchangeError,
'11022': AccountSuspended,
'11023': ExchangeError,
'11024': ExchangeError,
'11025': BadRequest,
'11026': ExchangeError,
'11027': BadSymbol,
'11028': BadSymbol,
'11029': ExchangeError,
'11030': ExchangeError,
'11031': DDoSProtection,
'11032': DDoSProtection,
'11033': DuplicateOrderId,
'11034': InvalidOrder,
'11035': InvalidOrder,
'11036': InvalidOrder,
'11037': InvalidOrder,
'11038': InvalidOrder,
'11039': InvalidOrder,
'11040': InvalidOrder,
'11041': InvalidOrder,
'11042': InvalidOrder,
'11043': InvalidOrder,
'11044': InvalidOrder,
'11045': InvalidOrder,
'11046': InvalidOrder,
'11047': InvalidOrder,
'11048': InvalidOrder,
'11049': InvalidOrder,
'11050': InvalidOrder,
'11051': InvalidOrder,
'11052': InvalidOrder,
'11053': InvalidOrder,
'11054': InvalidOrder,
'11055': InvalidOrder,
'11056': InvalidOrder,
'11057': InvalidOrder,
'11058': InvalidOrder,
'11059': InvalidOrder,
'11060': InvalidOrder,
'11061': CancelPending,
'11062': InvalidOrder,
'11063': InvalidOrder,
'11064': InvalidOrder,
'11065': InvalidOrder,
'11066': InvalidOrder,
'11067': InvalidOrder,
'11068': InvalidOrder,
'11069': ExchangeError,
'11070': BadSymbol,
'11071': InvalidOrder,
'11072': InvalidOrder,
'11073': InvalidOrder,
'11074': InvalidOrder,
'11075': InvalidOrder,
'11076': InvalidOrder,
'11077': InvalidOrder,
'11078': InvalidOrder,
'11079': InvalidOrder,
'11080': InvalidOrder,
'11081': InvalidOrder,
'11082': InsufficientFunds,
'11083': InvalidOrder,
'11084': InvalidOrder,
'11085': DuplicateOrderId,
'11086': InvalidOrder,
'11087': InvalidOrder,
'11088': InvalidOrder,
'11089': InvalidOrder,
'11090': InvalidOrder,
'11091': InvalidOrder,
'11092': InvalidOrder,
'11093': InvalidOrder,
'11094': InvalidOrder,
'11095': InvalidOrder,
'11096': InvalidOrder,
'11097': BadRequest,
'11098': BadRequest,
'11099': ExchangeError,
'11100': InsufficientFunds,
'11101': InsufficientFunds,
'11102': BadRequest,
'11103': BadRequest,
'11104': BadRequest,
'11105': InsufficientFunds,
'11106': InsufficientFunds,
'11107': ExchangeError,
'11108': InvalidOrder,
'11109': InvalidOrder,
'11110': InvalidOrder,
'11111': InvalidOrder,
'11112': InvalidOrder,
'11113': BadRequest,
'11114': InvalidOrder,
'11115': InvalidOrder,
'11116': InvalidOrder,
'11117': InvalidOrder,
'11118': InvalidOrder,
'11119': InvalidOrder,
'11120': InvalidOrder,
'11121': InvalidOrder,
'11122': InvalidOrder,
'11123': InvalidOrder,
'11124': InvalidOrder,
'11125': InvalidOrder,
'11126': InvalidOrder,
'11128': InvalidOrder,
'11129': InvalidOrder,
'11130': InvalidOrder,
'11131': InvalidOrder,
'11132': InvalidOrder,
'11133': InvalidOrder,
'11134': InvalidOrder,
// not documented
'30000': BadRequest,
'30018': BadRequest,
'34003': PermissionDenied,
'35104': InsufficientFunds,
'39995': RateLimitExceeded,
'39996': PermissionDenied, // {"code": "39996","msg": "Access denied."}
},
'broad': {
'401 Insufficient privilege': PermissionDenied,
'401 Request IP mismatch': PermissionDenied,
'Failed to find api-key': AuthenticationError,
'Missing required parameter': BadRequest,
'API Signature verification failed': AuthenticationError,
'Api key not found': AuthenticationError, // {"msg":"Api key not found 698dc9e3-6faa-4910-9476-12857e79e198","code":"10500"}
},
},
'options': {
'brokerId': 'ccxt2022',
'x-phemex-request-expiry': 60,
'createOrderByQuoteRequiresPrice': true,
'networks': {
'TRC20': 'TRX',
'ERC20': 'ETH',
},
'defaultNetworks': {
'USDT': 'ETH',
},
'defaultSubType': 'linear',
'accountsByType': {
'spot': 'spot',
'swap': 'future',
},
'transfer': {
'fillResponseFromRequest': true,
},
},
});
}
parseSafeNumber(value = undefined) {
if (value === undefined) {
return value;
}
let parts = value.split(',');
value = parts.join('');
parts = value.split(' ');
return this.safeNumber(parts, 0);
}
parseSwapMarket(market) {
//
// {
// "symbol":"BTCUSD",
// "displaySymbol":"BTC / USD",
// "indexSymbol":".BTC",
// "markSymbol":".MBTC",
// "fundingRateSymbol":".BTCFR",
// "fundingRate8hSymbol":".BTCFR8H",
// "contractUnderlyingAssets":"USD",
// "settleCurrency":"BTC",
// "quoteCurrency":"USD",
// "contractSize":"1 USD",
// "lotSize":1,
// "tickSize":0.5,
// "priceScale":4,
// "ratioScale":8,
// "pricePrecision":1,
// "minPriceEp":5000,
// "maxPriceEp":10000000000,
// "maxOrderQty":1000000,
// "type":"Perpetual",
// "status":"Listed",
// "tipOrderQty":1000000,
// "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.",
// }
//
const id = this.safeString(market, 'symbol');
const baseId = this.safeString2(market, 'baseCurrency', 'contractUnderlyingAssets');
const quoteId = this.safeString(market, 'quoteCurrency');
const settleId = this.safeString(market, 'settleCurrency');
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
const settle = this.safeCurrencyCode(settleId);
let inverse = false;
if (settleId !== quoteId) {
inverse = true;
}
const priceScale = this.safeInteger(market, 'priceScale');
const ratioScale = this.safeInteger(market, 'ratioScale');
const valueScale = this.safeInteger(market, 'valueScale');
const minPriceEp = this.safeString(market, 'minPriceEp');
const maxPriceEp = this.safeString(market, 'maxPriceEp');
const makerFeeRateEr = this.safeString(market, 'makerFeeRateEr');
const takerFeeRateEr = this.safeString(market, 'takerFeeRateEr');
const status = this.safeString(market, 'status');
const contractSizeString = this.safeString(market, 'contractSize', ' ');
let contractSize = undefined;
if (contractSizeString.indexOf(' ')) {
// "1 USD"
// "0.005 ETH"
const parts = contractSizeString.split(' ');
contractSize = this.parseNumber(parts[0]);
}
else {
// "1.0"
contractSize = this.parseNumber(contractSizeString);
}
return {
'id': id,
'symbol': base + '/' + quote + ':' + settle,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': settleId,
'type': 'swap',
'spot': false,
'margin': false,
'swap': true,
'future': false,
'option': false,
'active': status === 'Listed',
'contract': true,
'linear': !inverse,
'inverse': inverse,
'taker': this.parseNumber(this.fromEn(takerFeeRateEr, ratioScale)),
'maker': this.parseNumber(this.fromEn(makerFeeRateEr, ratioScale)),
'contractSize': contractSize,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'priceScale': priceScale,
'valueScale': valueScale,
'ratioScale': ratioScale,
'precision': {
'amount': this.safeNumber2(market, 'lotSize', 'qtyStepSize'),
'price': this.safeNumber(market, 'tickSize'),
},
'limits': {
'leverage': {
'min': this.parseNumber('1'),
'max': this.safeNumber(market, 'maxLeverage'),
},
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': this.parseNumber(this.fromEn(minPriceEp, priceScale)),
'max': this.parseNumber(this.fromEn(maxPriceEp, priceScale)),
},
'cost': {
'min': undefined,
'max': this.parseNumber(this.safeString(market, 'maxOrderQty')),
},
},
'info': market,
};
}
parseSpotMarket(market) {
//
// {
// "symbol":"sBTCUSDT",
// "code":1001,
// "displaySymbol":"BTC / USDT",
// "quoteCurrency":"USDT",
// "priceScale":8,
// "ratioScale":8,
// "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,
// "status":"Listed",
// "tipOrderQty":2,
// "description":"BTCUSDT is a BTC/USDT spot trading pair. Minimum order value is 1 USDT",
// "leverage":5
// "valueScale":8,
// },
//
const type = this.safeStringLower(market, 'type');
const id = this.safeString(market, 'symbol');
const quoteId = this.safeString(market, 'quoteCurrency');
const baseId = this.safeString(market, 'baseCurrency');
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
const status = this.safeString(market, 'status');
const precisionAmount = this.parseSafeNumber(this.safeString(market, 'baseTickSize'));
const precisionPrice = this.parseSafeNumber(this.safeString(market, 'quoteTickSize'));
return {
'id': id,
'symbol': base + '/' + quote,
'base': base,
'quote': quote,
'settle': undefined,
'baseId': baseId,
'quoteId': quoteId,
'settleId': undefined,
'type': type,
'spot': true,
'margin': false,
'swap': false,
'future': false,
'option': false,
'active': status === 'Listed',
'contract': false,
'linear': undefined,
'inverse': undefined,
'taker': this.safeNumber(market, 'defaultTakerFee'),
'maker': this.safeNumber(market, 'defaultMakerFee'),
'contractSize': undefined,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'priceScale': this.safeInteger(market, 'priceScale'),
'valueScale': this.safeInteger(market, 'valueScale'),
'ratioScale': this.safeInteger(market, 'ratioScale'),
'precision': {
'amount': precisionAmount,
'price': precisionPrice,
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': precisionAmount,
'max': this.parseSafeNumber(this.safeString(market, 'maxBaseOrderSize')),
},
'price': {
'min': precisionPrice,
'max': undefined,
},
'cost': {
'min': this.parseSafeNumber(this.safeString(market, 'minOrderValue')),
'max': this.parseSafeNumber(this.safeString(market, 'maxOrderValue')),
},
},
'info': market,
};
}
async fetchMarkets(params = {}) {
/**
* @method
* @name phemex#fetchMarkets
* @description retrieves data on all markets for phemex
* @param {object} params extra parameters specific to the exchange api endpoint
* @returns {[object]} an array of objects representing market data
*/
const v2Products = await this.publicGetCfgV2Products(params);
//
// {
// "code":0,
// "msg":"OK",
// "data":{
// "ratioScale":8,
// "currencies":[
// {"code":1,"currency":"BTC","valueScale":8,"minValueEv":1,"maxValueEv":5000000000000000000,"name":"Bitcoin"},
// {"code":2,"currency":"USD","valueScale":4,"minValueEv":1,"maxValueEv":500000000000000,"name":"USD"},
// {"code":3,"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",
// "code":1001,
// "displaySymbol":"BTC / USDT",
// "quoteCurrency":"USDT",
// "priceScale":8,
// "ratioScale":8,
// "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,
// "status":"Listed",
// "tipOrderQty":2,
// "description":"BTCUSDT is a BTC/USDT spot trading pair. Minimum order value is 1 USDT",
// "leverage":5
// },
// ],
// "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]},
// ]
// }
// }
//
const v1Products = await this.v1GetExchangePublicProducts(params);
const v1ProductsData = this.safeValue(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"
// },
// ]
// }
//
const v2ProductsData = this.safeValue(v2Products, 'data', {});
const products = this.safeValue(v2ProductsData, 'products', []);
const riskLimits = this.safeValue(v2ProductsData, 'riskLimits', []);
const currencies = this.safeValue(v2ProductsData, 'currencies', []);
const riskLimitsById = this.indexBy(riskLimits, 'symbol');
const v1ProductsById = this.indexBy(v1ProductsData, 'symbol');
const currenciesByCode = this.indexBy(currencies, 'currency');
const result = [];
for (let i = 0; i < products.length; i++) {
let market = products[i];
const type = this.safeStringLower(market, 'type');
if ((type === 'perpetual') || (type === 'perpetualv2')) {
const id = this.safeString(market, 'symbol');
const riskLimitValues = this.safeValue(riskLimitsById, id, {});
market = this.extend(market, riskLimitValues);
const v1ProductsValues = this.safeValue(v1ProductsById, id, {});
market = this.extend(market, v1ProductsValues);
market = this.parseSwapMarket(market);
}
else {
const baseCurrency = this.safeString(market, 'baseCurrency');
const currencyValues = this.safeValue(currenciesByCode, baseCurrency, {});
const valueScale = this.safeString(currencyValues, 'valueScale', '8');
market = this.extend(market, { 'valueScale': valueScale });
market = this.parseSpotMarket(market);
}
result.push(market);
}
return result;
}
async fetchCurrencies(params = {}) {
/**
* @method
* @name phemex#fetchCurrencies
* @description fetches all available currencies on an exchange
* @param {object} params extra parameters specific to the phemex api endpoint
* @returns {object} an associative dictionary of currencies
*/
const response = await this.publicGetCfgV2Products(params);
//
// {
// "code":0,
// "msg":"OK",
// "data":{
// ...,
// "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"},
// ],
// ...
// }
// }
const data = this.safeValue(response, 'data', {});
const currencies = this.safeValue(data, 'currencies', []);
const result = {};
for (let i = 0; i < currencies.length; i++) {
const currency = currencies[i];
const id = this.safeString(currency, 'currency');
const name = this.safeString(currency, 'name');
const code = this.safeCurrencyCode(id);
const valueScaleString = this.safeString(currency, 'valueScale');
const valueScale = parseInt(valueScaleString);
const minValueEv = this.safeString(currency, 'minValueEv');
const maxValueEv = this.safeString(currency, 'maxValueEv');
let minAmount = undefined;
let maxAmount = undefined;
let precision = undefined;
if (valueScale !== undefined) {
const precisionString = this.parsePrecision(valueScaleString);
precision = this.parseNumber(precisionString);
minAmount = this.parseNumber(Precise.stringMul(minValueEv, precisionString));
maxAmount = this.parseNumber(Precise.stringMul(maxValueEv, precisionString));
}
result[code] = {
'id': id,
'info': currency,
'code': code,
'name': name,
'active': undefined,
'deposit': undefined,
'withdraw': undefined,
'fee': undefined,
'precision': precision,
'limits': {
'amount': {
'min': minAmount,
'max': maxAmount,
},
'withdraw': {
'min': undefined,
'max': undefined,
},
},
'valueScale': valueScale,
'networks': {},
};
}
return result;
}
customParseBidAsk(bidask, priceKey = 0, amountKey = 1, market = undefined) {
if (market === undefined) {
throw new ArgumentsRequired(this.id + ' customParseBidAsk() requires a market argument');
}
let amount = this.safeString(bidask, amountKey);
if (market['spot']) {
amount = this.fromEv(amount, market);
}
return [
this.parseNumber(this.fromEp(this.safeString(bidask, priceKey), market)),
this.parseNumber(amount),
];
}
customParseOrderBook(orderbook, symbol, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1, market = undefined) {
const result = {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'nonce': undefined,
};
const sides = [bidsKey, asksKey];
for (let i = 0; i < sides.length; i++) {
const side = sides[i];
const orders = [];
const bidasks = this.safeValue(orderbook, side);
for (let k = 0; k < bidasks.length; k++) {
orders.push(this.customParseBidAsk(bidasks[k], priceKey, amountKey, market));
}
result[side] = orders;
}
result[bidsKey] = this.sortBy(result[bidsKey], 0, true);
result[asksKey] = this.sortBy(result[asksKey], 0);
return result;
}
async fetchOrderBook(symbol, limit = undefined, params = {}) {
/**
* @method
* @name phemex#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#queryorderbook
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int|undefined} limit the maximum amount of order book entries to return
* @param {object} params extra parameters specific to the phemex api endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'symbol': market['id'],
// 'id': 123456789, // optional request id
};
let method = 'v1GetMdOrderbook';
if (market['linear'] && market['settle'] === 'USDT') {
method = 'v2GetMdV2Orderbook';
}
const response = await this[method](this.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"
// }
// }
//
const result = this.safeValue(response, 'result', {});
const book = this.safeValue2(result, 'book', 'orderbook_p', {});
const timestamp = this.safeIntegerProduct(result, 'timestamp', 0.000001);
const orderbook = this.customParseOrderBook(book, symbol, timestamp, 'bids', 'asks', 0, 1, market);
orderbook['nonce'] = this.safeInteger(result, 'sequence');
return orderbook;
}
toEn(n, scale) {
const stringN = n.toString();
const precise = new Precise(stringN);
precise.decimals = precise.decimals - scale;
precise.reduce();
const preciseString = precise.toString();
return this.parseToInt(preciseString);
}
toEv(amount, market = undefined) {
if ((amount === undefined) || (market === undefined)) {
return amount;
}
return this.toEn(amount, market['valueScale']);
}
toEp(price, market = undefined) {
if ((price === undefined) || (market === undefined)) {
return price;
}
return this.toEn(price, market['priceScale']);
}
fromEn(en, scale) {
if (en === undefined) {
return undefined;
}
const precise = new Precise(en);
precise.decimals = this.sum(precise.decimals, scale);
precise.reduce();
return precise.toString();
}
fromEp(ep, market = undefined) {
if ((ep === undefined) || (market === undefined)) {
return ep;
}
return this.fromEn(ep, this.safeInteger(market, 'priceScale'));
}
fromEv(ev, market = undefined) {
if ((ev === undefined) || (market === undefined)) {
return ev;
}
return this.fromEn(ev, this.safeInteger(market, 'valueScale'));
}
fromEr(er, market = undefined) {
if ((er === undefined) || (market === undefined)) {
return er;
}
return this.fromEn(er, this.safeInteger(market, 'ratioScale'));
}
parseOHLCV(ohlcv, market = undefined) {
//
// [
// 1592467200, // timestamp
// 300, // interval
// 23376000000, // last
// 23322000000, // open
// 23381000000, // high
// 23315000000, // low
// 23367000000, // close
// 208671000, // base volume
// 48759063370, // quote volume
// ]
//
let baseVolume = undefined;
if ((market !== undefined) && market['spot']) {
baseVolume = this.parseNumber(this.fromEv(this.safeString(ohlcv, 7), market));
}
else {
baseVolume = this.safeNumber(ohlcv, 7);
}
return [
this.safeTimestamp(ohlcv, 0),
this.parseNumber(this.fromEp(this.safeString(ohlcv, 3), market)),
this.parseNumber(this.fromEp(this.safeString(ohlcv, 4), market)),
this.parseNumber(this.fromEp(this.safeString(ohlcv, 5), market)),
this.parseNumber(this.fromEp(this.safeString(ohlcv, 6), market)),
baseVolume,
];
}
async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name phemex#fetchOHLCV
* @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Hedged-Perpetual-API.md#querykline
* @see https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#query-kline
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents
* @param {int|undefined} since *emulated not supported by the exchange* timestamp in ms of the earliest candle to fetch
* @param {int|undefined} limit the maximum amount of candles to fetch
* @param {object} params extra parameters specific to the phemex api endpoint
* @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
await this.loadMarkets();
const market = this.market(symbol);
const userLimit = limit;
const request = {
'symbol': market['id'],
'resolution': this.safeString(this.timeframes, timeframe, timeframe),
};
const possibleLimitValues = [5, 10, 50, 100, 500, 1000];
const maxLimit = 1000;
if (limit === undefined && since === undefined) {
limit = possibleLimitValues[5];
}
if (since !== undefined) {
// phemex also