UNPKG

@madnai/ccxt

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

903 lines (882 loc) 116 kB
'use strict'; // ---------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, BadSymbol, AuthenticationError, InsufficientFunds, InvalidOrder, ArgumentsRequired, OrderNotFound, BadRequest, PermissionDenied, AccountSuspended, CancelPending, DDoSProtection, DuplicateOrderId, NotSupported } = require ('./base/errors'); const { TICK_SIZE, ROUND, DECIMAL_PLACES } = require ('./base/functions/number'); // ---------------------------------------------------------------------------- module.exports = class phemex extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'phemex', 'name': 'Phemex', 'countries': [ 'CN' ], // China 'rateLimit': 100, 'version': 'v1', 'certified': false, 'pro': true, 'has': { 'cancelAllOrders': true, // swap contracts only 'cancelOrder': true, 'createOrder': true, 'fetchBalance': true, 'fetchClosedOrders': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDeposits': true, 'fetchMarkets': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchTicker': true, 'fetchTrades': true, 'fetchWithdrawals': true, }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/85225056-221eb600-b3d7-11ea-930d-564d2690e3f6.jpg', 'test': { 'v1': 'https://testnet-api.phemex.com/v1', 'public': 'https://testnet-api.phemex.com/exchange/public', 'private': 'https://testnet-api.phemex.com', }, 'api': { 'v1': 'https://api.phemex.com/v1', 'public': 'https://api.phemex.com/exchange/public', 'private': 'https://api.phemex.com', }, 'www': 'https://phemex.com', 'doc': 'https://github.com/phemex/phemex-api-docs', 'fees': 'https://phemex.com/fees-conditions', 'referral': 'https://phemex.com/register?referralCode=EDNVJ', }, 'timeframes': { '1m': '60', '3m': '180', '5m': '300', '15m': '900', '30m': '1800', '1h': '3600', '2h': '7200', '3h': '10800', '4h': '14400', '6h': '21600', '12h': '43200', '1d': '86400', '1w': '604800', '1M': '2592000', }, 'api': { 'public': { 'get': [ 'cfg/v2/products', // spot + contracts 'products', // contracts only 'nomics/trades', // ?market=<symbol>&since=<since> 'md/kline', // ?from=1589811875&resolution=1800&symbol=sBTCUSDT&to=1592457935 ], }, 'v1': { 'get': [ 'md/orderbook', // ?symbol=<symbol>&id=<id> 'md/trade', // ?symbol=<symbol>&id=<id> 'md/ticker/24hr', // ?symbol=<symbol>&id=<id> 'md/ticker/24hr/all', // ?id=<id> 'md/spot/ticker/24hr', // ?symbol=<symbol>&id=<id> 'md/spot/ticker/24hr/all', // ?symbol=<symbol>&id=<id> 'exchange/public/products', // contracts only ], }, 'private': { 'get': [ // spot 'spot/orders/active', // ?symbol=<symbol>&orderID=<orderID> // 'spot/orders/active', // ?symbol=<symbol>&clOrDID=<clOrdID> 'spot/orders', // ?symbol=<symbol> 'spot/wallets', // ?currency=<currency> 'exchange/spot/order', // ?symbol=<symbol>&ordStatus=<ordStatus1,orderStatus2>ordType=<ordType1,orderType2>&start=<start>&end=<end>&limit=<limit>&offset=<offset> 'exchange/spot/order/trades', // ?symbol=<symbol>&start=<start>&end=<end>&limit=<limit>&offset=<offset> // swap 'accounts/accountPositions', // ?currency=<currency> 'orders/activeList', // ?symbol=<symbol> 'exchange/order/list', // ?symbol=<symbol>&start=<start>&end=<end>&offset=<offset>&limit=<limit>&ordStatus=<ordStatus>&withCount=<withCount> 'exchange/order', // ?symbol=<symbol>&orderID=<orderID1,orderID2> // 'exchange/order', // ?symbol=<symbol>&clOrdID=<clOrdID1,clOrdID2> 'exchange/order/trade', // ?symbol=<symbol>&start=<start>&end=<end>&limit=<limit>&offset=<offset>&withCount=<withCount> 'phemex-user/users/children', // ?offset=<offset>&limit=<limit>&withCount=<withCount> 'phemex-user/wallets/v2/depositAddress', // ?_t=1592722635531&currency=USDT 'exchange/margins/transfer', // ?start=<start>&end=<end>&offset=<offset>&limit=<limit>&withCount=<withCount> 'exchange/wallets/confirm/withdraw', // ?code=<withdrawConfirmCode> 'exchange/wallets/withdrawList', // ?currency=<currency>&limit=<limit>&offset=<offset>&withCount=<withCount> 'exchange/wallets/depositList', // ?currency=<currency>&offset=<offset>&limit=<limit> 'exchange/wallets/v2/depositAddress', // ?currency=<currency> ], 'post': [ // spot 'spot/orders', // swap 'orders', 'positions/assign', // ?symbol=<symbol>&posBalance=<posBalance>&posBalanceEv=<posBalanceEv> 'exchange/wallets/transferOut', 'exchange/wallets/transferIn', 'exchange/margins', 'exchange/wallets/createWithdraw', // ?otpCode=<otpCode> 'exchange/wallets/cancelWithdraw', 'exchange/wallets/createWithdrawAddress', // ?otpCode={optCode} ], 'put': [ // spot 'spot/orders', // ?symbol=<symbol>&orderID=<orderID>&origClOrdID=<origClOrdID>&clOrdID=<clOrdID>&priceEp=<priceEp>&baseQtyEV=<baseQtyEV>&quoteQtyEv=<quoteQtyEv>&stopPxEp=<stopPxEp> // swap 'orders/replace', // ?symbol=<symbol>&orderID=<orderID>&origClOrdID=<origClOrdID>&clOrdID=<clOrdID>&price=<price>&priceEp=<priceEp>&orderQty=<orderQty>&stopPx=<stopPx>&stopPxEp=<stopPxEp>&takeProfit=<takeProfit>&takeProfitEp=<takeProfitEp>&stopLoss=<stopLoss>&stopLossEp=<stopLossEp>&pegOffsetValueEp=<pegOffsetValueEp>&pegPriceType=<pegPriceType> 'positions/leverage', // ?symbol=<symbol>&leverage=<leverage>&leverageEr=<leverageEr> 'positions/riskLimit', // ?symbol=<symbol>&riskLimit=<riskLimit>&riskLimitEv=<riskLimitEv> ], 'delete': [ // spot 'spot/orders', // ?symbol=<symbol>&orderID=<orderID> // 'spot/orders', // ?symbol=<symbol>&clOrdID=<clOrdID> // swap 'orders/cancel', // ?symbol=<symbol>&orderID=<orderID> 'orders', // ?symbol=<symbol>&orderID=<orderID1>,<orderID2>,<orderID3> 'orders/all', // ?symbol=<symbol>&untriggered=<untriggered>&text=<text> ], }, }, 'precisionMode': TICK_SIZE, 'fees': { 'trading': { 'tierBased': false, 'percentage': true, 'taker': 0.1 / 100, 'maker': 0.1 / 100, }, }, 'requiredCredentials': { 'apiKey': true, 'secret': true, }, 'exceptions': { 'exact': { // not documented '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 this 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 this 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 // not documented '30018': BadRequest, // {"code":30018,"msg":"phemex.data.size.uplimt","data":null} '39996': PermissionDenied, // {"code": "39996","msg": "Access denied."} }, 'broad': { '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} }, }, 'options': { 'x-phemex-request-expiry': 60, // in seconds 'createOrderByQuoteRequiresPrice': true, }, }); } parseSafeFloat (value = undefined) { if (value === undefined) { return value; } value = value.replace (',', ''); const parts = value.split (' '); return this.safeFloat (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" // "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.safeString (market, 'baseCurrency', 'contractUnderlyingAssets'); const quoteId = this.safeString (market, 'quoteCurrency'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const symbol = base + '/' + quote; const type = this.safeStringLower (market, 'type'); let taker = undefined; let maker = undefined; let inverse = false; const spot = false; const swap = true; const settleCurrency = this.safeString (market, 'settleCurrency'); if (settleCurrency !== quoteId) { inverse = true; } const linear = !inverse; const precision = { 'amount': this.safeFloat (market, 'lotSize'), 'price': this.safeFloat (market, 'tickSize'), }; const priceScale = this.safeInteger (market, 'priceScale'); const ratioScale = this.safeInteger (market, 'ratioScale'); const minPriceEp = this.safeFloat (market, 'minPriceEp'); const maxPriceEp = this.safeFloat (market, 'maxPriceEp'); const makerFeeRateEr = this.safeFloat (market, 'makerFeeRateEr'); const takerFeeRateEr = this.safeFloat (market, 'takerFeeRateEr'); if (makerFeeRateEr !== undefined) { maker = this.fromEn (makerFeeRateEr, ratioScale, 0.00000001); } if (takerFeeRateEr !== undefined) { taker = this.fromEn (takerFeeRateEr, ratioScale, 0.00000001); } const limits = { 'amount': { 'min': precision['amount'], 'max': undefined, }, 'price': { 'min': this.fromEn (minPriceEp, priceScale, precision['price']), 'max': this.fromEn (maxPriceEp, priceScale, precision['price']), }, 'cost': { 'min': undefined, 'max': this.parseSafeFloat (this.safeString (market, 'maxOrderQty')), }, }; const active = undefined; return { 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'info': market, 'type': type, 'spot': spot, 'swap': swap, 'linear': linear, 'inverse': inverse, 'active': active, 'taker': taker, 'maker': maker, 'priceScale': priceScale, 'valueScale': 0, 'ratioScale': ratioScale, 'precision': precision, 'limits': limits, }; } parseSpotMarket (market) { // // { // "symbol":"sBTCUSDT", // "displaySymbol":"BTC / USDT", // "quoteCurrency":"USDT", // "pricePrecision":2, // "type":"Spot", // "baseCurrency":"BTC", // "baseTickSize":"0.000001 BTC", // "baseTickSizeEv":100, // "quoteTickSize":"0.01 USDT", // "quoteTickSizeEv":1000000, // "minOrderValue":"10 USDT", // "minOrderValueEv":1000000000, // "maxBaseOrderSize":"1000 BTC", // "maxBaseOrderSizeEv":100000000000, // "maxOrderValue":"5,000,000 USDT", // "maxOrderValueEv":500000000000000, // "defaultTakerFee":"0.001", // "defaultTakerFeeEr":100000, // "defaultMakerFee":"0.001", // "defaultMakerFeeEr":100000, // "baseQtyPrecision":6, // "quoteQtyPrecision":2 // } // 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 linear = undefined; const inverse = undefined; const spot = true; const swap = false; const taker = this.safeFloat (market, 'defaultTakerFee'); const maker = this.safeFloat (market, 'defaultMakerFee'); const precision = { 'amount': this.parseSafeFloat (this.safeString (market, 'baseTickSize')), 'price': this.parseSafeFloat (this.safeString (market, 'quoteTickSize')), }; const limits = { 'amount': { 'min': precision['amount'], 'max': this.parseSafeFloat (this.safeString (market, 'maxBaseOrderSize')), }, 'price': { 'min': precision['price'], 'max': undefined, }, 'cost': { 'min': this.parseSafeFloat (this.safeString (market, 'minOrderValue')), 'max': this.parseSafeFloat (this.safeString (market, 'maxOrderValue')), }, }; const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const symbol = base + '/' + quote; const active = undefined; return { 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'info': market, 'type': type, 'spot': spot, 'swap': swap, 'linear': linear, 'inverse': inverse, 'active': active, 'taker': taker, 'maker': maker, 'precision': precision, 'priceScale': 8, 'valueScale': 8, 'ratioScale': 8, 'limits': limits, }; } async fetchMarkets (params = {}) { const v2Products = await this.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]}, // ] // } // } // 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 riskLimitsById = this.indexBy (riskLimits, 'symbol'); const v1ProductsById = this.indexBy (v1ProductsData, 'symbol'); const result = []; for (let i = 0; i < products.length; i++) { let market = products[i]; const type = this.safeStringLower (market, 'type'); if (type === 'perpetual') { 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 { market = this.parseSpotMarket (market); } result.push (market); } return result; } async fetchCurrencies (params = {}) { 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 valueScale = this.safeInteger (currency, 'valueScale'); const minValueEv = this.safeFloat (currency, 'minValueEv'); const maxValueEv = this.safeFloat (currency, 'maxValueEv'); let minAmount = undefined; let maxAmount = undefined; let precision = undefined; if (valueScale !== undefined) { precision = Math.pow (10, -valueScale); precision = parseFloat (this.decimalToPrecision (precision, ROUND, 0.00000001, this.precisionMode)); if (minValueEv !== undefined) { minAmount = parseFloat (this.decimalToPrecision (minValueEv * precision, ROUND, 0.00000001, this.precisionMode)); } if (maxValueEv !== undefined) { maxAmount = parseFloat (this.decimalToPrecision (maxValueEv * precision, ROUND, 0.00000001, this.precisionMode)); } } result[code] = { 'id': id, 'info': currency, 'code': code, 'name': name, 'active': undefined, 'fee': undefined, 'precision': precision, 'limits': { 'amount': { 'min': minAmount, 'max': maxAmount, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }, 'valueScale': valueScale, }; } return result; } parseBidAsk (bidask, priceKey = 0, amountKey = 1, market = undefined) { if (market === undefined) { throw new ArgumentsRequired (this.id + ' parseBidAsk requires a market argument'); } return [ this.fromEp (this.safeFloat (bidask, priceKey), market), this.fromEv (this.safeFloat (bidask, amountKey), market), ]; } parseOrderBook (orderbook, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1, market = undefined) { const result = { '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.parseBidAsk (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 = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], // 'id': 123456789, // optional request id }; const response = await this.v1GetMdOrderbook (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.safeValue (result, 'book', {}); const timestamp = this.safeIntegerProduct (result, 'timestamp', 0.000001); const orderbook = this.parseOrderBook (book, timestamp, 'bids', 'asks', 0, 1, market); orderbook['nonce'] = this.safeInteger (result, 'sequence'); return orderbook; } toEn (n, scale, precision) { return parseInt (this.decimalToPrecision (n * Math.pow (10, scale), ROUND, precision, DECIMAL_PLACES)); } toEv (amount, market = undefined) { if ((amount === undefined) || (market === undefined)) { return amount; } return this.toEn (amount, market['valueScale'], 0); } toEp (price, market = undefined) { if ((price === undefined) || (market === undefined)) { return price; } return this.toEn (price, market['priceScale'], 0); } fromEn (en, scale, precision, precisionMode = undefined) { precisionMode = (precisionMode === undefined) ? this.precisionMode : precisionMode; return parseFloat (this.decimalToPrecision (en * Math.pow (10, -scale), ROUND, precision, precisionMode)); } fromEp (ep, market = undefined) { if ((ep === undefined) || (market === undefined)) { return ep; } return this.fromEn (ep, market['priceScale'], market['precision']['price']); } fromEv (ev, market = undefined) { if ((ev === undefined) || (market === undefined)) { return ev; } return this.fromEn (ev, market['valueScale'], market['precision']['amount']); } fromEr (er, market = undefined) { if ((er === undefined) || (market === undefined)) { return er; } return this.fromEn (er, market['ratioScale'], 0.00000001); } parseOHLCV (ohlcv, market = undefined) { // // [ // 1592467200, // timestamp // 300, // interval // 23376000000, // last // 23322000000, // open // 23381000000, // high // 23315000000, // low // 23367000000, // close // 208671000, // base volume // 48759063370, // quote volume // ] // return [ this.safeTimestamp (ohlcv, 0), this.fromEp (this.safeFloat (ohlcv, 3), market), this.fromEp (this.safeFloat (ohlcv, 4), market), this.fromEp (this.safeFloat (ohlcv, 5), market), this.fromEp (this.safeFloat (ohlcv, 6), market), this.fromEv (this.safeFloat (ohlcv, 7), market), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { const request = { // 'symbol': market['id'], 'resolution': this.timeframes[timeframe], // 'from': 1588830682, // seconds // 'to': this.seconds (), }; const duration = this.parseTimeframe (timeframe); const now = this.seconds (); if (since !== undefined) { if (limit === undefined) { limit = 2000; // max 2000 } since = parseInt (since / 1000); request['from'] = since; request['to'] = this.sum (since, duration * limit); } else if (limit !== undefined) { limit = Math.min (limit, 2000); request['from'] = now - duration * this.sum (limit, 1); request['to'] = now; } else { throw new ArgumentsRequired (this.id + ' fetchOHLCV requires a since argument, or a limit argument, or both'); } await this.loadMarkets (); const market = this.market (symbol); request['symbol'] = market['id']; const response = await this.publicGetMdKline (this.extend (request, params)); // // { // "code":0, // "msg":"OK", // "data":{ // "total":-1, // "rows":[ // [1592467200,300,23376000000,23322000000,23381000000,23315000000,23367000000,208671000,48759063370], //