UNPKG

sfccxt

Version:

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

1,146 lines (1,125 loc) 126 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { TICK_SIZE } = require ('./base/functions/number'); const { AuthenticationError, BadRequest, DDoSProtection, ExchangeError, ExchangeNotAvailable, InsufficientFunds, InvalidOrder, OrderNotFound, PermissionDenied, ArgumentsRequired, BadSymbol } = require ('./base/errors'); const Precise = require ('./base/Precise'); // --------------------------------------------------------------------------- module.exports = class bitmex extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'bitmex', 'name': 'BitMEX', 'countries': [ 'SC' ], // Seychelles 'version': 'v1', 'userAgent': undefined, // cheapest endpoints are 10 requests per second (trading) // 10 per second => rateLimit = 1000ms / 10 = 100ms // 120 per minute => 2 per second => weight = 5 (authenticated) // 30 per minute => 0.5 per second => weight = 20 (unauthenticated) 'rateLimit': 100, 'pro': true, 'has': { 'CORS': undefined, 'spot': false, 'margin': false, 'swap': true, 'future': true, 'option': false, 'addMargin': undefined, 'cancelAllOrders': true, 'cancelOrder': true, 'cancelOrders': true, 'createOrder': true, 'createReduceOnlyOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchClosedOrders': true, 'fetchDepositAddress': true, 'fetchDepositAddresses': false, 'fetchDepositAddressesByNetwork': false, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': true, 'fetchFundingRates': true, 'fetchIndexOHLCV': false, 'fetchLedger': true, 'fetchLeverage': false, 'fetchLeverageTiers': false, 'fetchMarketLeverageTiers': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchPosition': false, 'fetchPositions': true, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTransactions': 'emulated', 'fetchTransfer': false, 'fetchTransfers': false, 'reduceMargin': undefined, 'setLeverage': true, 'setMargin': undefined, 'setMarginMode': true, 'setPositionMode': false, 'transfer': false, 'withdraw': true, }, 'timeframes': { '1m': '1m', '5m': '5m', '1h': '1h', '1d': '1d', }, 'urls': { 'test': { 'public': 'https://testnet.bitmex.com', 'private': 'https://testnet.bitmex.com', }, 'logo': 'https://user-images.githubusercontent.com/1294454/27766319-f653c6e6-5ed4-11e7-933d-f0bc3699ae8f.jpg', 'api': { 'public': 'https://www.bitmex.com', 'private': 'https://www.bitmex.com', }, 'www': 'https://www.bitmex.com', 'doc': [ 'https://www.bitmex.com/app/apiOverview', 'https://github.com/BitMEX/api-connectors/tree/master/official-http', ], 'fees': 'https://www.bitmex.com/app/fees', 'referral': 'https://www.bitmex.com/register/upZpOX', }, 'api': { 'public': { 'get': { 'announcement': 5, 'announcement/urgent': 5, 'funding': 5, 'instrument': 5, 'instrument/active': 5, 'instrument/activeAndIndices': 5, 'instrument/activeIntervals': 5, 'instrument/compositeIndex': 5, 'instrument/indices': 5, 'insurance': 5, 'leaderboard': 5, 'liquidation': 5, 'orderBook': 5, 'orderBook/L2': 5, 'quote': 5, 'quote/bucketed': 5, 'schema': 5, 'schema/websocketHelp': 5, 'settlement': 5, 'stats': 5, 'stats/history': 5, 'trade': 5, 'trade/bucketed': 5, 'wallet/assets': 5, 'wallet/networks': 5, }, }, 'private': { 'get': { 'apiKey': 5, 'chat': 5, 'chat/channels': 5, 'chat/connected': 5, 'execution': 5, 'execution/tradeHistory': 5, 'notification': 5, 'order': 5, 'position': 5, 'user': 5, 'user/affiliateStatus': 5, 'user/checkReferralCode': 5, 'user/commission': 5, 'user/depositAddress': 5, 'user/executionHistory': 5, 'user/margin': 5, 'user/minWithdrawalFee': 5, 'user/wallet': 5, 'user/walletHistory': 5, 'user/walletSummary': 5, 'wallet/assets': 5, 'wallet/networks': 5, 'userEvent': 5, }, 'post': { 'apiKey': 5, 'apiKey/disable': 5, 'apiKey/enable': 5, 'chat': 5, 'order': 1, 'order/bulk': 5, 'order/cancelAllAfter': 5, 'order/closePosition': 5, 'position/isolate': 1, 'position/leverage': 1, 'position/riskLimit': 5, 'position/transferMargin': 1, 'user/cancelWithdrawal': 5, 'user/confirmEmail': 5, 'user/confirmEnableTFA': 5, 'user/confirmWithdrawal': 5, 'user/disableTFA': 5, 'user/logout': 5, 'user/logoutAll': 5, 'user/preferences': 5, 'user/requestEnableTFA': 5, 'user/requestWithdrawal': 5, }, 'put': { 'order': 1, 'order/bulk': 5, 'user': 5, }, 'delete': { 'apiKey': 5, 'order': 1, 'order/all': 1, }, }, }, 'exceptions': { 'exact': { 'Invalid API Key.': AuthenticationError, 'This key is disabled.': PermissionDenied, 'Access Denied': PermissionDenied, 'Duplicate clOrdID': InvalidOrder, 'orderQty is invalid': InvalidOrder, 'Invalid price': InvalidOrder, 'Invalid stopPx for ordType': InvalidOrder, }, 'broad': { 'Signature not valid': AuthenticationError, 'overloaded': ExchangeNotAvailable, 'Account has insufficient Available Balance': InsufficientFunds, 'Service unavailable': ExchangeNotAvailable, // {"error":{"message":"Service unavailable","name":"HTTPError"}} 'Server Error': ExchangeError, // {"error":{"message":"Server Error","name":"HTTPError"}} 'Unable to cancel order due to existing state': InvalidOrder, 'We require all new traders to verify': PermissionDenied, // {"message":"We require all new traders to verify their identity before their first deposit. Please visit bitmex.com/verify to complete the process.","name":"HTTPError"} }, }, 'precisionMode': TICK_SIZE, 'options': { // https://blog.bitmex.com/api_announcement/deprecation-of-api-nonce-header/ // https://github.com/ccxt/ccxt/issues/4789 'api-expires': 5, // in seconds 'fetchOHLCVOpenTimestamp': true, 'networks': { 'BTC': 'btc', 'ETH': 'eth', 'BSC': 'bsc', 'BNB': 'bsc', 'TRON': 'tron', 'ERC20': 'eth', 'BEP20': 'bsc', 'TRC20': 'tron', 'TRX': 'tron', 'AVAX': 'avax', 'NEAR': 'near', 'XTZ': 'xtz', 'DOT': 'dot', 'SOL': 'sol', }, 'networksById': { 'btc': 'BTC', 'eth': 'ERC20', 'bsc': 'BSC', 'tron': 'TRX', 'avax': 'AVAX', 'near': 'NEAR', 'xtz': 'XTZ', 'dot': 'DOT', 'sol': 'SOL', }, }, 'commonCurrencies': { 'USDt': 'USDT', 'XBt': 'BTC', 'XBT': 'BTC', 'Gwei': 'ETH', 'GWEI': 'ETH', 'LAMP': 'SOL', 'LAMp': 'SOL', }, }); } async fetchMarkets (params = {}) { /** * @method * @name bitmex#fetchMarkets * @description retrieves data on all markets for bitmex * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ const response = await this.publicGetInstrumentActiveAndIndices (params); // // { // "symbol": "LTCUSDT", // "rootSymbol": "LTC", // "state": "Open", // "typ": "FFWCSX", // "listing": "2021-11-10T04:00:00.000Z", // "front": "2021-11-10T04:00:00.000Z", // "expiry": null, // "settle": null, // "listedSettle": null, // "relistInterval": null, // "inverseLeg": "", // "sellLeg": "", // "buyLeg": "", // "optionStrikePcnt": null, // "optionStrikeRound": null, // "optionStrikePrice": null, // "optionMultiplier": null, // "positionCurrency": "LTC", // "underlying": "LTC", // "quoteCurrency": "USDT", // "underlyingSymbol": "LTCT=", // "reference": "BMEX", // "referenceSymbol": ".BLTCT", // "calcInterval": null, // "publishInterval": null, // "publishTime": null, // "maxOrderQty": 1000000000, // "maxPrice": 1000000, // "lotSize": 1000, // "tickSize": 0.01, // "multiplier": 100, // "settlCurrency": "USDt", // "underlyingToPositionMultiplier": 10000, // "underlyingToSettleMultiplier": null, // "quoteToSettleMultiplier": 1000000, // "isQuanto": false, // "isInverse": false, // "initMargin": 0.03, // "maintMargin": 0.015, // "riskLimit": 1000000000000, // "riskStep": 1000000000000, // "limit": null, // "capped": false, // "taxed": true, // "deleverage": true, // "makerFee": -0.0001, // "takerFee": 0.0005, // "settlementFee": 0, // "insuranceFee": 0, // "fundingBaseSymbol": ".LTCBON8H", // "fundingQuoteSymbol": ".USDTBON8H", // "fundingPremiumSymbol": ".LTCUSDTPI8H", // "fundingTimestamp": "2022-01-14T20:00:00.000Z", // "fundingInterval": "2000-01-01T08:00:00.000Z", // "fundingRate": 0.0001, // "indicativeFundingRate": 0.0001, // "rebalanceTimestamp": null, // "rebalanceInterval": null, // "openingTimestamp": "2022-01-14T17:00:00.000Z", // "closingTimestamp": "2022-01-14T18:00:00.000Z", // "sessionInterval": "2000-01-01T01:00:00.000Z", // "prevClosePrice": 138.511, // "limitDownPrice": null, // "limitUpPrice": null, // "bankruptLimitDownPrice": null, // "bankruptLimitUpPrice": null, // "prevTotalVolume": 12699024000, // "totalVolume": 12702160000, // "volume": 3136000, // "volume24h": 114251000, // "prevTotalTurnover": 232418052349000, // "totalTurnover": 232463353260000, // "turnover": 45300911000, // "turnover24h": 1604331340000, // "homeNotional24h": 11425.1, // "foreignNotional24h": 1604331.3400000003, // "prevPrice24h": 135.48, // "vwap": 140.42165, // "highPrice": 146.42, // "lowPrice": 135.08, // "lastPrice": 144.36, // "lastPriceProtected": 144.36, // "lastTickDirection": "MinusTick", // "lastChangePcnt": 0.0655, // "bidPrice": 143.75, // "midPrice": 143.855, // "askPrice": 143.96, // "impactBidPrice": 143.75, // "impactMidPrice": 143.855, // "impactAskPrice": 143.96, // "hasLiquidity": true, // "openInterest": 38103000, // "openValue": 547963053300, // "fairMethod": "FundingRate", // "fairBasisRate": 0.1095, // "fairBasis": 0.004, // "fairPrice": 143.811, // "markMethod": "FairPrice", // "markPrice": 143.811, // "indicativeTaxRate": null, // "indicativeSettlePrice": 143.807, // "optionUnderlyingPrice": null, // "settledPriceAdjustmentRate": null, // "settledPrice": null, // "timestamp": "2022-01-14T17:49:55.000Z" // } // const result = []; for (let i = 0; i < response.length; i++) { const market = response[i]; const id = this.safeString (market, 'symbol'); const baseId = this.safeString (market, 'underlying'); const quoteId = this.safeString (market, 'quoteCurrency'); const settleId = this.safeString (market, 'settlCurrency', ''); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const settle = this.safeCurrencyCode (settleId); const basequote = baseId + quoteId; const swap = (id === basequote); // 'positionCurrency' may be empty ("", as Bitmex currently returns for ETHUSD) // so let's take the settlCurrency first and then adjust if needed let type = undefined; let future = false; let prediction = false; let index = false; let symbol = base + '/' + quote + ':' + settle; const expiryDatetime = this.safeString (market, 'expiry'); const expiry = this.parse8601 (expiryDatetime); const inverse = this.safeValue (market, 'isInverse'); const status = this.safeString (market, 'state'); let active = status !== 'Unlisted'; if (swap) { type = 'swap'; } else if (id.indexOf ('B_') >= 0) { prediction = true; type = 'prediction'; symbol = id; } else if (expiry !== undefined) { future = true; type = 'future'; symbol = symbol + '-' + this.yymmdd (expiry); } else { index = true; type = 'index'; symbol = id; active = false; } const positionId = this.safeString2 (market, 'positionCurrency', 'underlying'); const position = this.safeCurrencyCode (positionId); const positionIsQuote = (position === quote); const maxOrderQty = this.safeNumber (market, 'maxOrderQty'); const contract = !index; const initMargin = this.safeString (market, 'initMargin', '1'); const maxLeverage = this.parseNumber (Precise.stringDiv ('1', initMargin)); const multiplierString = Precise.stringAbs (this.safeString (market, 'multiplier')); result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': type, 'spot': false, 'margin': false, 'swap': swap, 'future': future, 'option': false, 'prediction': prediction, 'index': index, 'active': active, 'contract': contract, 'linear': contract ? !inverse : undefined, 'inverse': contract ? inverse : undefined, 'taker': this.safeNumber (market, 'takerFee'), 'maker': this.safeNumber (market, 'makerFee'), 'contractSize': this.parseNumber (multiplierString), 'expiry': expiry, 'expiryDatetime': expiryDatetime, 'strike': this.safeNumber (market, 'optionStrikePrice'), 'optionType': undefined, 'precision': { 'amount': this.safeNumber (market, 'lotSize'), 'price': this.safeNumber (market, 'tickSize'), 'quote': this.safeNumber (market, 'tickSize'), 'base': this.safeNumber (market, 'tickSize'), }, 'limits': { 'leverage': { 'min': contract ? this.parseNumber ('1') : undefined, 'max': contract ? maxLeverage : undefined, }, 'amount': { 'min': undefined, 'max': positionIsQuote ? undefined : maxOrderQty, }, 'price': { 'min': undefined, 'max': this.safeNumber (market, 'maxPrice'), }, 'cost': { 'min': undefined, 'max': positionIsQuote ? maxOrderQty : undefined, }, }, 'info': market, }); } return result; } parseBalance (response) { // // [ // { // "account":1455728, // "currency":"XBt", // "riskLimit":1000000000000, // "prevState":"", // "state":"", // "action":"", // "amount":263542, // "pendingCredit":0, // "pendingDebit":0, // "confirmedDebit":0, // "prevRealisedPnl":0, // "prevUnrealisedPnl":0, // "grossComm":0, // "grossOpenCost":0, // "grossOpenPremium":0, // "grossExecCost":0, // "grossMarkValue":0, // "riskValue":0, // "taxableMargin":0, // "initMargin":0, // "maintMargin":0, // "sessionMargin":0, // "targetExcessMargin":0, // "varMargin":0, // "realisedPnl":0, // "unrealisedPnl":0, // "indicativeTax":0, // "unrealisedProfit":0, // "syntheticMargin":null, // "walletBalance":263542, // "marginBalance":263542, // "marginBalancePcnt":1, // "marginLeverage":0, // "marginUsedPcnt":0, // "excessMargin":263542, // "excessMarginPcnt":1, // "availableMargin":263542, // "withdrawableMargin":263542, // "timestamp":"2020-08-03T12:01:01.246Z", // "grossLastValue":0, // "commission":null // } // ] // const result = { 'info': response }; for (let i = 0; i < response.length; i++) { const balance = response[i]; const currencyId = this.safeString (balance, 'currency'); const code = this.safeCurrencyCode (currencyId); const account = this.account (); let free = this.safeString (balance, 'availableMargin'); let total = this.safeString (balance, 'marginBalance'); if (code !== 'USDT') { free = Precise.stringDiv (free, '1e8'); total = Precise.stringDiv (total, '1e8'); } else { free = Precise.stringDiv (free, '1e6'); total = Precise.stringDiv (total, '1e6'); } account['free'] = free; account['total'] = total; result[code] = account; } return this.safeBalance (result); } async fetchBalance (params = {}) { /** * @method * @name bitmex#fetchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ await this.loadMarkets (); const request = { 'currency': 'all', }; const response = await this.privateGetUserMargin (this.extend (request, params)); // // [ // { // "account":1455728, // "currency":"XBt", // "riskLimit":1000000000000, // "prevState":"", // "state":"", // "action":"", // "amount":263542, // "pendingCredit":0, // "pendingDebit":0, // "confirmedDebit":0, // "prevRealisedPnl":0, // "prevUnrealisedPnl":0, // "grossComm":0, // "grossOpenCost":0, // "grossOpenPremium":0, // "grossExecCost":0, // "grossMarkValue":0, // "riskValue":0, // "taxableMargin":0, // "initMargin":0, // "maintMargin":0, // "sessionMargin":0, // "targetExcessMargin":0, // "varMargin":0, // "realisedPnl":0, // "unrealisedPnl":0, // "indicativeTax":0, // "unrealisedProfit":0, // "syntheticMargin":null, // "walletBalance":263542, // "marginBalance":263542, // "marginBalancePcnt":1, // "marginLeverage":0, // "marginUsedPcnt":0, // "excessMargin":263542, // "excessMarginPcnt":1, // "availableMargin":263542, // "withdrawableMargin":263542, // "timestamp":"2020-08-03T12:01:01.246Z", // "grossLastValue":0, // "commission":null // } // ] // return this.parseBalance (response); } async fetchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name bitmex#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @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 bitmex api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols */ await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['depth'] = limit; } const response = await this.publicGetOrderBookL2 (this.extend (request, params)); const result = { 'symbol': symbol, 'bids': [], 'asks': [], 'timestamp': undefined, 'datetime': undefined, 'nonce': undefined, }; for (let i = 0; i < response.length; i++) { const order = response[i]; const side = (order['side'] === 'Sell') ? 'asks' : 'bids'; const amount = this.safeNumber (order, 'size'); const price = this.safeNumber (order, 'price'); // https://github.com/ccxt/ccxt/issues/4926 // https://github.com/ccxt/ccxt/issues/4927 // the exchange sometimes returns null price in the orderbook if (price !== undefined) { result[side].push ([ price, amount ]); } } result['bids'] = this.sortBy (result['bids'], 0, true); result['asks'] = this.sortBy (result['asks'], 0); return result; } async fetchOrder (id, symbol = undefined, params = {}) { /** * @method * @name bitmex#fetchOrder * @description fetches information on an order made by the user * @param {string|undefined} symbol unified symbol of the market the order was made in * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {object} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure} */ const filter = { 'filter': { 'orderID': id, }, }; const response = await this.fetchOrders (symbol, undefined, undefined, this.deepExtend (filter, params)); const numResults = response.length; if (numResults === 1) { return response[0]; } throw new OrderNotFound (this.id + ': The order ' + id + ' not found.'); } async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitmex#fetchOrders * @description fetches information on multiple orders made by the user * @param {string|undefined} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure} */ await this.loadMarkets (); let market = undefined; let request = {}; if (symbol !== undefined) { market = this.market (symbol); request['symbol'] = market['id']; } if (since !== undefined) { request['startTime'] = this.iso8601 (since); } if (limit !== undefined) { request['count'] = limit; } request = this.deepExtend (request, params); // why the hassle? urlencode in python is kinda broken for nested dicts. // E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}" // Bitmex doesn't like that. Hence resorting to this hack. if ('filter' in request) { request['filter'] = this.json (request['filter']); } const response = await this.privateGetOrder (request); return this.parseOrders (response, market, since, limit); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitmex#fetchOpenOrders * @description fetch all unfilled currently open orders * @param {string|undefined} symbol unified market symbol * @param {int|undefined} since the earliest time in ms to fetch open orders for * @param {int|undefined} limit the maximum number of open orders structures to retrieve * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure} */ const request = { 'filter': { 'open': true, }, }; return await this.fetchOrders (symbol, since, limit, this.deepExtend (request, params)); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitmex#fetchClosedOrders * @description fetches information on multiple closed orders made by the user * @param {string|undefined} symbol unified market symbol of the market orders were made in * @param {int|undefined} since the earliest time in ms to fetch orders for * @param {int|undefined} limit the maximum number of orde structures to retrieve * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure} */ // Bitmex barfs if you set 'open': false in the filter... const orders = await this.fetchOrders (symbol, since, limit, params); return this.filterBy (orders, 'status', 'closed'); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitmex#fetchMyTrades * @description fetch all trades made by the user * @param {string|undefined} symbol unified market symbol * @param {int|undefined} since the earliest time in ms to fetch trades for * @param {int|undefined} limit the maximum number of trades structures to retrieve * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html#trade-structure} */ await this.loadMarkets (); let market = undefined; let request = {}; if (symbol !== undefined) { market = this.market (symbol); request['symbol'] = market['id']; } if (since !== undefined) { request['startTime'] = this.iso8601 (since); } if (limit !== undefined) { request['count'] = limit; } request = this.deepExtend (request, params); // why the hassle? urlencode in python is kinda broken for nested dicts. // E.g. self.urlencode({"filter": {"open": True}}) will return "filter={'open':+True}" // Bitmex doesn't like that. Hence resorting to this hack. if ('filter' in request) { request['filter'] = this.json (request['filter']); } const response = await this.privateGetExecutionTradeHistory (request); // // [ // { // "execID": "string", // "orderID": "string", // "clOrdID": "string", // "clOrdLinkID": "string", // "account": 0, // "symbol": "string", // "side": "string", // "lastQty": 0, // "lastPx": 0, // "underlyingLastPx": 0, // "lastMkt": "string", // "lastLiquidityInd": "string", // "simpleOrderQty": 0, // "orderQty": 0, // "price": 0, // "displayQty": 0, // "stopPx": 0, // "pegOffsetValue": 0, // "pegPriceType": "string", // "currency": "string", // "settlCurrency": "string", // "execType": "string", // "ordType": "string", // "timeInForce": "string", // "execInst": "string", // "contingencyType": "string", // "exDestination": "string", // "ordStatus": "string", // "triggered": "string", // "workingIndicator": true, // "ordRejReason": "string", // "simpleLeavesQty": 0, // "leavesQty": 0, // "simpleCumQty": 0, // "cumQty": 0, // "avgPx": 0, // "commission": 0, // "tradePublishIndicator": "string", // "multiLegReportingType": "string", // "text": "string", // "trdMatchID": "string", // "execCost": 0, // "execComm": 0, // "homeNotional": 0, // "foreignNotional": 0, // "transactTime": "2019-03-05T12:47:02.762Z", // "timestamp": "2019-03-05T12:47:02.762Z" // } // ] // return this.parseTrades (response, market, since, limit); } parseLedgerEntryType (type) { const types = { 'Withdrawal': 'transaction', 'RealisedPNL': 'margin', 'UnrealisedPNL': 'margin', 'Deposit': 'transaction', 'Transfer': 'transfer', 'AffiliatePayout': 'referral', }; return this.safeString (types, type, type); } parseLedgerEntry (item, currency = undefined) { // // { // transactID: "69573da3-7744-5467-3207-89fd6efe7a47", // account: 24321, // currency: "XBt", // transactType: "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ... // amount: -1000000, // fee: 300000, // transactStatus: "Completed", // "Canceled", ... // address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9", // tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA", // text: "", // transactTime: "2017-03-21T20:05:14.388Z", // walletBalance: 0, // balance after // marginBalance: null, // timestamp: "2017-03-22T13:09:23.514Z" // } // // ButMEX returns the unrealized pnl from the wallet history endpoint. // The unrealized pnl transaction has an empty timestamp. // It is not related to historical pnl it has status set to "Pending". // Therefore it's not a part of the history at all. // https://github.com/ccxt/ccxt/issues/6047 // // { // "transactID":"00000000-0000-0000-0000-000000000000", // "account":121210, // "currency":"XBt", // "transactType":"UnrealisedPNL", // "amount":-5508, // "fee":0, // "transactStatus":"Pending", // "address":"XBTUSD", // "tx":"", // "text":"", // "transactTime":null, # ←---------------------------- null // "walletBalance":139198767, // "marginBalance":139193259, // "timestamp":null # ←---------------------------- null // } // const id = this.safeString (item, 'transactID'); const account = this.safeString (item, 'account'); const referenceId = this.safeString (item, 'tx'); const referenceAccount = undefined; const type = this.parseLedgerEntryType (this.safeString (item, 'transactType')); const currencyId = this.safeString (item, 'currency'); const code = this.safeCurrencyCode (currencyId, currency); let amount = this.safeNumber (item, 'amount'); if (amount !== undefined) { amount = amount / 100000000; } let timestamp = this.parse8601 (this.safeString (item, 'transactTime')); if (timestamp === undefined) { // https://github.com/ccxt/ccxt/issues/6047 // set the timestamp to zero, 1970 Jan 1 00:00:00 // for unrealized pnl and other transactions without a timestamp timestamp = 0; // see comments above } let feeCost = this.safeNumber (item, 'fee', 0); if (feeCost !== undefined) { feeCost = feeCost / 100000000; } const fee = { 'cost': feeCost, 'currency': code, }; let after = this.safeNumber (item, 'walletBalance'); if (after !== undefined) { after = after / 100000000; } const before = this.sum (after, -amount); let direction = undefined; if (amount < 0) { direction = 'out'; amount = Math.abs (amount); } else { direction = 'in'; } const status = this.parseTransactionStatus (this.safeString (item, 'transactStatus')); return { 'id': id, 'info': item, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'direction': direction, 'account': account, 'referenceId': referenceId, 'referenceAccount': referenceAccount, 'type': type, 'currency': code, 'amount': amount, 'before': before, 'after': after, 'status': status, 'fee': fee, }; } async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitmex#fetchLedger * @description fetch the history of changes, actions done by the user or operations that altered balance of the user * @param {string|undefined} code unified currency code, default is undefined * @param {int|undefined} since timestamp in ms of the earliest ledger entry, default is undefined * @param {int|undefined} limit max number of ledger entrys to return, default is undefined * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {object} a [ledger structure]{@link https://docs.ccxt.com/en/latest/manual.html#ledger-structure} */ await this.loadMarkets (); let currency = undefined; if (code !== undefined) { currency = this.currency (code); } const request = { // 'start': 123, }; // // if (since !== undefined) { // // date-based pagination not supported // } // if (limit !== undefined) { request['count'] = limit; } const response = await this.privateGetUserWalletHistory (this.extend (request, params)); // // [ // { // transactID: "69573da3-7744-5467-3207-89fd6efe7a47", // account: 24321, // currency: "XBt", // transactType: "Withdrawal", // "AffiliatePayout", "Transfer", "Deposit", "RealisedPNL", ... // amount: -1000000, // fee: 300000, // transactStatus: "Completed", // "Canceled", ... // address: "1Ex4fkF4NhQaQdRWNoYpqiPbDBbq18Kdd9", // tx: "3BMEX91ZhhKoWtsH9QRb5dNXnmnGpiEetA", // text: "", // transactTime: "2017-03-21T20:05:14.388Z", // walletBalance: 0, // balance after // marginBalance: null, // timestamp: "2017-03-22T13:09:23.514Z" // } // ] // return this.parseLedger (response, currency, since, limit); } async fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {}) { /** * @method * @name bitmex#fetchTransactions * @description fetch history of deposits and withdrawals * @param {string|undefined} code unified currency code for the currency of the transactions, default is undefined * @param {int|undefined} since timestamp in ms of the earliest transaction, default is undefined * @param {int|undefined} limit max number of transactions to return, default is undefined * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {object} a list of [transaction structure]{@link https://docs.ccxt.com/en/latest/manual.html#transaction-structure} */ await this.loadMarkets (); const request = { 'currency': 'all', // 'start': 123, }; // // if (since !== undefined) { // // date-based pagination not supported // } // if (limit !== undefined) { request['count'] = limit; } const response = await this.privateGetUserWalletHistory (this.extend (request, params)); const transactions = this.filterByArray (response, 'transactType', [ 'Withdrawal', 'Deposit' ], false); let currency = undefined; if (code !== undefined) { currency = this.currency (code); } return this.parseTransactions (transactions, currency, since, limit); } parseTransactionStatus (status) { const statuses = { 'Canceled': 'canceled', 'Completed': 'ok', 'Pending': 'pending', }; return this.safeString (statuses, status, status); } parseTransaction (transaction, currency = undefined) { // // { // 'transactID': 'ffe699c2-95ee-4c13-91f9-0faf41daec25', // 'account': 123456, // 'currency': 'XBt', // 'network':'', // 'transactType': 'Withdrawal', // 'amount': -100100000, // 'fee': 100000, // 'transactStatus': 'Completed', // 'address': '385cR5DM96n1HvBDMzLHPYcw89fZAXULJP', // 'tx': '3BMEXabcdefghijklmnopqrstuvwxyz123', // 'text': '', // 'transactTime': '2019-01-02T01:00:00.000Z', // 'walletBalance': 99900000, // 'marginBalance': None, // 'timestamp': '2019-01-02T13:00:00.000Z' // } // const currencyId = this.safeString (transaction, 'currency'); currency = this.safeCurrency (currencyId, currency); // For deposits, transactTime == timestamp // For withdrawals, transactTime is submission, timestamp is processed const transactTime = this.parse8601 (this.safeString (transaction, 'transactTime')); const timestamp = this.parse8601 (this.safeString (transaction, 'timestamp')); const type = this.safeStringLower (transaction, 'transactType'); // Deposits have no from address or to address, withdrawals have both let address = undefined; let addressFrom = undefined; let addressTo = undefined; if (type === 'withdrawal') { address = this.safeString (transaction, 'address'); addressFrom = this.safeString (transaction, 'tx'); addressTo = address; } let amountString = this.safeString (transaction, 'amount'); const scale = (currency['code'] === 'BTC') ? '1e8' : '1e6'; amountString = Precise.stringDiv (Precise.stringAbs (amountString), scale); let feeCostString = this.safeString (transaction, 'fee'); feeCostString = Precise.stringDiv (feeCostString, scale); let status = this.safeString (transaction, 'transactStatus'); if (status !== undefined) { status = this.parseTransactionStatus (status); } return { 'info': transaction, 'id': this.safeString (transaction, 'transactID'), 'txid': this.safeString (transaction, 'tx'), 'type': type, 'currency': currency['code'], 'network': this.safeString (transaction, 'status'), 'amount': this.parseNumber (amountString), 'status': status, 'timestamp': transactTime, 'datetime': this.iso8601 (transactTime), 'address': address, 'addressFrom': addressFrom, 'addressTo': addressTo, 'tag': undefined, 'tagFrom': undefined, 'tagTo': undefined, 'updated': timestamp, 'comment': undefined, 'fee': { 'currency': currency['code'], 'cost': this.parseNumber (feeCostString), 'rate': undefined, }, }; } async fetchTicker (symbol, params = {}) { /** * @method * @name bitmex#fetchTicker * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure} */ await this.loadMarkets (); const market = this.market (symbol); const tickers = await this.fetchTickers ([ market['symbol'] ], params); const ticker = this.safeValue (tickers, market['symbol']); if (ticker === undefined) { throw new BadSymbol (this.id + ' fetchTicker() symbol ' + symbol + ' not found'); } return ticker; } async fetchTickers (symbols = undefined, params = {}) { /** * @method * @name bitmex#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} params extra parameters specific to the bitmex api endpoint * @returns {object} an array of [ticker structures]{@link https://docs.ccxt.c