UNPKG

consequunturatque

Version:

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

1,192 lines (1,166 loc) 69.5 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 } = 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, 'rateLimit': 2000, 'pro': true, 'has': { 'cancelAllOrders': true, 'cancelOrder': true, 'CORS': false, 'createOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchClosedOrders': true, 'fetchLedger': true, 'fetchMarkets': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTransactions': 'emulated', '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', 'announcement/urgent', 'funding', 'instrument', 'instrument/active', 'instrument/activeAndIndices', 'instrument/activeIntervals', 'instrument/compositeIndex', 'instrument/indices', 'insurance', 'leaderboard', 'liquidation', 'orderBook', 'orderBook/L2', 'quote', 'quote/bucketed', 'schema', 'schema/websocketHelp', 'settlement', 'stats', 'stats/history', 'trade', 'trade/bucketed', ], }, 'private': { 'get': [ 'apiKey', 'chat', 'chat/channels', 'chat/connected', 'execution', 'execution/tradeHistory', 'notification', 'order', 'position', 'user', 'user/affiliateStatus', 'user/checkReferralCode', 'user/commission', 'user/depositAddress', 'user/executionHistory', 'user/margin', 'user/minWithdrawalFee', 'user/wallet', 'user/walletHistory', 'user/walletSummary', ], 'post': [ 'apiKey', 'apiKey/disable', 'apiKey/enable', 'chat', 'order', 'order/bulk', 'order/cancelAllAfter', 'order/closePosition', 'position/isolate', 'position/leverage', 'position/riskLimit', 'position/transferMargin', 'user/cancelWithdrawal', 'user/confirmEmail', 'user/confirmEnableTFA', 'user/confirmWithdrawal', 'user/disableTFA', 'user/logout', 'user/logoutAll', 'user/preferences', 'user/requestEnableTFA', 'user/requestWithdrawal', ], 'put': [ 'order', 'order/bulk', 'user', ], 'delete': [ 'apiKey', 'order', 'order/all', ], }, }, '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, }, }, '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, }, }); } async fetchMarkets (params = {}) { const response = await this.publicGetInstrumentActiveAndIndices (params); const result = []; for (let i = 0; i < response.length; i++) { const market = response[i]; const active = (market['state'] !== 'Unlisted'); const id = market['symbol']; const baseId = market['underlying']; const quoteId = market['quoteCurrency']; const basequote = baseId + quoteId; const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const swap = (id === basequote); // 'positionCurrency' may be empty ("", as Bitmex currently returns for ETHUSD) // so let's take the quote currency first and then adjust if needed const positionId = this.safeString2 (market, 'positionCurrency', 'quoteCurrency'); let type = undefined; let future = false; let prediction = false; const position = this.safeCurrencyCode (positionId); let symbol = id; if (swap) { type = 'swap'; symbol = base + '/' + quote; } else if (id.indexOf ('B_') >= 0) { prediction = true; type = 'prediction'; } else { future = true; type = 'future'; } const precision = { 'amount': undefined, 'price': undefined, }; const lotSize = this.safeNumber (market, 'lotSize'); const tickSize = this.safeNumber (market, 'tickSize'); if (lotSize !== undefined) { precision['amount'] = lotSize; } if (tickSize !== undefined) { precision['price'] = tickSize; } const limits = { 'amount': { 'min': undefined, 'max': undefined, }, 'price': { 'min': tickSize, 'max': this.safeNumber (market, 'maxPrice'), }, 'cost': { 'min': undefined, 'max': undefined, }, }; const limitField = (position === quote) ? 'cost' : 'amount'; limits[limitField] = { 'min': lotSize, 'max': this.safeNumber (market, 'maxOrderQty'), }; result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'active': active, 'precision': precision, 'limits': limits, 'taker': this.safeNumber (market, 'takerFee'), 'maker': this.safeNumber (market, 'makerFee'), 'type': type, 'spot': false, 'swap': swap, 'future': future, 'prediction': prediction, 'info': market, }); } return result; } parseBalanceResponse (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 === 'BTC') { free = Precise.stringDiv (free, '1e8'); total = Precise.stringDiv (total, '1e8'); } account['free'] = free; account['total'] = total; result[code] = account; } return this.parseBalance (result, false); } async fetchBalance (params = {}) { 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.parseBalanceResponse (response); } async fetchOrderBook (symbol, limit = undefined, params = {}) { 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 = {}) { 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 = {}) { 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 = {}) { 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 = {}) { // 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 = {}) { 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 = {}) { 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 = {}) { await this.loadMarkets (); 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)); 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', // '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 id = this.safeString (transaction, 'transactID'); // 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 amount = this.safeInteger (transaction, 'amount'); if (amount !== undefined) { amount = Math.abs (amount) / 10000000; } let feeCost = this.safeInteger (transaction, 'fee'); if (feeCost !== undefined) { feeCost = feeCost / 10000000; } const fee = { 'cost': feeCost, 'currency': 'BTC', }; let status = this.safeString (transaction, 'transactStatus'); if (status !== undefined) { status = this.parseTransactionStatus (status); } return { 'info': transaction, 'id': id, 'txid': undefined, 'timestamp': transactTime, 'datetime': this.iso8601 (transactTime), 'addressFrom': addressFrom, 'address': address, 'addressTo': addressTo, 'tagFrom': undefined, 'tag': undefined, 'tagTo': undefined, 'type': type, 'amount': amount, // BTC is the only currency on Bitmex 'currency': 'BTC', 'status': status, 'updated': timestamp, 'comment': undefined, 'fee': fee, }; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const market = this.market (symbol); if (!market['active']) { throw new ExchangeError (this.id + ': symbol ' + symbol + ' is delisted'); } const tickers = await this.fetchTickers ([ symbol ], params); const ticker = this.safeValue (tickers, symbol); if (ticker === undefined) { throw new ExchangeError (this.id + ' ticker symbol ' + symbol + ' not found'); } return ticker; } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); const response = await this.publicGetInstrumentActiveAndIndices (params); const result = {}; for (let i = 0; i < response.length; i++) { const ticker = this.parseTicker (response[i]); const symbol = this.safeString (ticker, 'symbol'); if (symbol !== undefined) { result[symbol] = ticker; } } return this.filterByArray (result, 'symbol', symbols); } parseTicker (ticker, market = undefined) { // // { symbol: "ETHH19", // rootSymbol: "ETH", // state: "Open", // typ: "FFCCSX", // listing: "2018-12-17T04:00:00.000Z", // front: "2019-02-22T12:00:00.000Z", // expiry: "2019-03-29T12:00:00.000Z", // settle: "2019-03-29T12:00:00.000Z", // relistInterval: null, // inverseLeg: "", // sellLeg: "", // buyLeg: "", // optionStrikePcnt: null, // optionStrikeRound: null, // optionStrikePrice: null, // optionMultiplier: null, // positionCurrency: "ETH", // underlying: "ETH", // quoteCurrency: "XBT", // underlyingSymbol: "ETHXBT=", // reference: "BMEX", // referenceSymbol: ".BETHXBT30M", // calcInterval: null, // publishInterval: null, // publishTime: null, // maxOrderQty: 100000000, // maxPrice: 10, // lotSize: 1, // tickSize: 0.00001, // multiplier: 100000000, // settlCurrency: "XBt", // underlyingToPositionMultiplier: 1, // underlyingToSettleMultiplier: null, // quoteToSettleMultiplier: 100000000, // isQuanto: false, // isInverse: false, // initMargin: 0.02, // maintMargin: 0.01, // riskLimit: 5000000000, // riskStep: 5000000000, // limit: null, // capped: false, // taxed: true, // deleverage: true, // makerFee: -0.0005, // takerFee: 0.0025, // settlementFee: 0, // insuranceFee: 0, // fundingBaseSymbol: "", // fundingQuoteSymbol: "", // fundingPremiumSymbol: "", // fundingTimestamp: null, // fundingInterval: null, // fundingRate: null, // indicativeFundingRate: null, // rebalanceTimestamp: null, // rebalanceInterval: null, // openingTimestamp: "2019-02-13T08:00:00.000Z", // closingTimestamp: "2019-02-13T09:00:00.000Z", // sessionInterval: "2000-01-01T01:00:00.000Z", // prevClosePrice: 0.03347, // limitDownPrice: null, // limitUpPrice: null, // bankruptLimitDownPrice: null, // bankruptLimitUpPrice: null, // prevTotalVolume: 1386531, // totalVolume: 1387062, // volume: 531, // volume24h: 17118, // prevTotalTurnover: 4741294246000, // totalTurnover: 4743103466000, // turnover: 1809220000, // turnover24h: 57919845000, // homeNotional24h: 17118, // foreignNotional24h: 579.19845, // prevPrice24h: 0.03349, // vwap: 0.03383564, // highPrice: 0.03458, // lowPrice: 0.03329, // lastPrice: 0.03406, // lastPriceProtected: 0.03406, // lastTickDirection: "ZeroMinusTick", // lastChangePcnt: 0.017, // bidPrice: 0.03406, // midPrice: 0.034065, // askPrice: 0.03407, // impactBidPrice: 0.03406, // impactMidPrice: 0.034065, // impactAskPrice: 0.03407, // hasLiquidity: true, // openInterest: 83679, // openValue: 285010674000, // fairMethod: "ImpactMidPrice", // fairBasisRate: 0, // fairBasis: 0, // fairPrice: 0.03406, // markMethod: "FairPrice", // markPrice: 0.03406, // indicativeTaxRate: 0, // indicativeSettlePrice: 0.03406, // optionUnderlyingPrice: null, // settledPrice: null, // timestamp: "2019-02-13T08:40:30.000Z", // } // let symbol = undefined; const marketId = this.safeString (ticker, 'symbol'); market = this.safeValue (this.markets_by_id, marketId, market); if (market !== undefined) { symbol = market['symbol']; } const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp')); const open = this.safeNumber (ticker, 'prevPrice24h'); const last = this.safeNumber (ticker, 'lastPrice'); let change = undefined; let percentage = undefined; if (last !== undefined && open !== undefined) { change = last - open; if (open > 0) { percentage = change / open * 100; } } return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeNumber (ticker, 'highPrice'), 'low': this.safeNumber (ticker, 'lowPrice'), 'bid': this.safeNumber (ticker, 'bidPrice'), 'bidVolume': undefined, 'ask': this.safeNumber (ticker, 'askPrice'), 'askVolume': undefined, 'vwap': this.safeNumber (ticker, 'vwap'), 'open': open, 'close': last, 'last': last, 'previousClose': undefined, 'change': change, 'percentage': percentage, 'average': this.sum (open, last) / 2, 'baseVolume': this.safeNumber (ticker, 'homeNotional24h'), 'quoteVolume': this.safeNumber (ticker, 'foreignNotional24h'), 'info': ticker, }; } parseOHLCV (ohlcv, market = undefined) { // // { // "timestamp":"2015-09-25T13:38:00.000Z", // "symbol":"XBTUSD", // "open":237.45, // "high":237.45, // "low":237.45, // "close":237.45, // "trades":0, // "volume":0, // "vwap":null, // "lastSize":null, // "turnover":0, // "homeNotional":0, // "foreignNotional":0 // } // return [ this.parse8601 (this.safeString (ohlcv, 'timestamp')), this.safeNumber (ohlcv, 'open'), this.safeNumber (ohlcv, 'high'), this.safeNumber (ohlcv, 'low'), this.safeNumber (ohlcv, 'close'), this.safeNumber (ohlcv, 'volume'), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); // send JSON key/value pairs, such as {"key": "value"} // filter by individual fields and do advanced queries on timestamps // let filter = { 'key': 'value' }; // send a bare series (e.g. XBU) to nearest expiring contract in that series // you can also send a timeframe, e.g. XBU:monthly // timeframes: daily, weekly, monthly, quarterly, and biquarterly const market = this.market (symbol); const request = { 'symbol': market['id'], 'binSize': this.timeframes[timeframe], 'partial': true, // true == include yet-incomplete current bins // 'filter': filter, // filter by individual fields and do advanced queries // 'columns': [], // will return all columns if omitted // 'start': 0, // starting point for results (wtf?) // 'reverse': false, // true == newest first // 'endTime': '', // ending date filter for results }; if (limit !== undefined) { request['count'] = limit; // default 100, max 500 } const duration = this.parseTimeframe (timeframe) * 1000; const fetchOHLCVOpenTimestamp = this.safeValue (this.options, 'fetchOHLCVOpenTimestamp', true); // if since is not set, they will return candles starting from 2017-01-01 if (since !== undefined) { let timestamp = since; if (fetchOHLCVOpenTimestamp) { timestamp = this.sum (timestamp, duration); } const ymdhms = this.ymdhms (timestamp); request['startTime'] = ymdhms; // starting date filter for results } else { request['reverse'] = true; } const response = await this.publicGetTradeBucketed (this.extend (request, params)); // // [ // {"timestamp":"2015-09-25T13:38:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}, // {"timestamp":"2015-09-25T13:39:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0}, // {"timestamp":"2015-09-25T13:40:00.000Z","symbol":"XBTUSD","open":237.45,"high":237.45,"low":237.45,"close":237.45,"trades":0,"volume":0,"vwap":null,"lastSize":null,"turnover":0,"homeNotional":0,"foreignNotional":0} // ] // const result = this.parseOHLCVs (response, market, timeframe, since, limit); if (fetchOHLCVOpenTimestamp) { // bitmex returns the candle's close timestamp - https://github.com/ccxt/ccxt/issues/4446 // we can emulate the open timestamp by shifting all the timestamps one place // so the previous close becomes the current open, and we drop the first candle for (let i = 0; i < result.length; i++) { result[i][0] = result[i][0] - duration; } } return result; } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // { // timestamp: '2018-08-28T00:00:02.735Z', // symbol: 'XBTUSD', // side: 'Buy', // size: 2000, // price: 6906.5, // tickDirection: 'PlusTick', // trdMatchID: 'b9a42432-0a46-6a2f-5ecc-c32e9ca4baf8', // grossValue: 28958000, // homeNotional: 0.28958, // foreignNotional: 2000 // } // // fetchMyTrades (private) // // { // "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" // } // const timestamp = this.parse8601 (this.safeString (trade, 'timestamp')); const price = this.safeNumber2 (trade, 'avgPx', 'price'); const amount = this.safeNumber2 (trade, 'size', 'lastQty'); const id = this.safeString (trade, 'trdMatchID'); const order = this.safeString (trade, 'orderID'); const side = this.safeStringLower (trade, 'side'); // price * amount doesn't work for all symbols (e.g. XBT, ETH) let cost = this.safeNumber (trade, 'execCost'); if (cost !== undefined) { cost = Math.abs (cost) / 100000000; } let fee = undefined; if ('execComm' in trade) { let feeCost = this.safeNumber (trade, 'execComm'); feeCost = feeCost / 100000000; const currencyId = this.safeString (trade, 'settlCurrency'); const feeCurrency = this.safeCurrencyCode (currencyId); const feeRate = this.safeNumber (trade, 'commission'); fee = { 'cost': feeCost, 'currency': feeCurrency, 'rate': feeRate, }; } let takerOrMaker = undefined; if (fee !== undefined) { takerOrMaker = (fee['cost'] < 0) ? 'maker' : 'taker'; } const marketId = this.safeString (trade, 'symbol'); const symbol = this.safeSymbol (marketId, market); const type = this.safeStringLower (trade, 'ordType'); return { 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'id': id, 'order': order, 'type': type, 'takerOrMaker': takerOrMaker, 'side': side, 'price': price, 'cost': cost, 'amount': amount, 'fee': fee, }; } pars