UNPKG

consequunturatque

Version:

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

1,302 lines (1,130 loc) 64.2 kB
"use strict"; // ---------------------------------------------------------------------------- const functions = require ('./functions') const { isNode , keys , values , deepExtend , extend , clone , flatten , unique , indexBy , sortBy , groupBy , aggregate , uuid , unCamelCase , precisionFromString , throttle , capitalize , now , timeout , TimedOut , buildOHLCVC , decimalToPrecision , defaultFetch } = functions const { // eslint-disable-line object-curly-newline ExchangeError , BadSymbol , InvalidAddress , NotSupported , AuthenticationError , DDoSProtection , RequestTimeout , ExchangeNotAvailable , RateLimitExceeded } = require ('./errors') const { TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING } = functions.precisionConstants const BN = require ('../static_dependencies/BN/bn') const Precise = require ('./Precise') // ---------------------------------------------------------------------------- module.exports = class Exchange { describe () { return { 'id': undefined, 'name': undefined, 'countries': undefined, 'enableRateLimit': false, 'rateLimit': 2000, // milliseconds = seconds * 1000 'certified': false, 'pro': false, 'has': { 'loadMarkets': true, 'cancelAllOrders': false, 'cancelOrder': true, 'cancelOrders': false, 'CORS': false, 'createDepositAddress': false, 'createLimitOrder': true, 'createMarketOrder': true, 'createOrder': true, 'deposit': false, 'editOrder': 'emulated', 'fetchBalance': true, 'fetchBidsAsks': false, 'fetchClosedOrders': false, 'fetchCurrencies': false, 'fetchDepositAddress': false, 'fetchDeposits': false, 'fetchFundingFees': false, 'fetchL2OrderBook': true, 'fetchLedger': false, 'fetchMarkets': true, 'fetchMyTrades': false, 'fetchOHLCV': 'emulated', 'fetchOpenOrders': false, 'fetchOrder': false, 'fetchOrderBook': true, 'fetchOrderBooks': false, 'fetchOrders': false, 'fetchOrderTrades': false, 'fetchStatus': 'emulated', 'fetchTicker': true, 'fetchTickers': false, 'fetchTime': false, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': false, 'fetchTradingLimits': false, 'fetchTransactions': false, 'fetchWithdrawals': false, 'privateAPI': true, 'publicAPI': true, 'signIn': false, 'withdraw': false, }, 'urls': { 'logo': undefined, 'api': undefined, 'www': undefined, 'doc': undefined, 'fees': undefined, }, 'api': undefined, 'requiredCredentials': { 'apiKey': true, 'secret': true, 'uid': false, 'login': false, 'password': false, 'twofa': false, // 2-factor authentication (one-time password key) 'privateKey': false, // a "0x"-prefixed hexstring private key for a wallet 'walletAddress': false, // the wallet address "0x"-prefixed hexstring 'token': false, // reserved for HTTP auth in some cases }, 'markets': undefined, // to be filled manually or by fetchMarkets 'currencies': {}, // to be filled manually or by fetchMarkets 'timeframes': undefined, // redefine if the exchange has.fetchOHLCV 'fees': { 'trading': { 'tierBased': undefined, 'percentage': undefined, 'taker': undefined, 'maker': undefined, }, 'funding': { 'tierBased': undefined, 'percentage': undefined, 'withdraw': {}, 'deposit': {}, }, }, 'status': { 'status': 'ok', 'updated': undefined, 'eta': undefined, 'url': undefined, }, 'exceptions': undefined, 'httpExceptions': { '422': ExchangeError, '418': DDoSProtection, '429': RateLimitExceeded, '404': ExchangeNotAvailable, '409': ExchangeNotAvailable, '410': ExchangeNotAvailable, '500': ExchangeNotAvailable, '501': ExchangeNotAvailable, '502': ExchangeNotAvailable, '520': ExchangeNotAvailable, '521': ExchangeNotAvailable, '522': ExchangeNotAvailable, '525': ExchangeNotAvailable, '526': ExchangeNotAvailable, '400': ExchangeNotAvailable, '403': ExchangeNotAvailable, '405': ExchangeNotAvailable, '503': ExchangeNotAvailable, '530': ExchangeNotAvailable, '408': RequestTimeout, '504': RequestTimeout, '401': AuthenticationError, '511': AuthenticationError, }, // some exchanges report only 'free' on `fetchBlance` call (i.e. report no 'used' funds) // in this case ccxt will try to infer 'used' funds from open order cache, which might be stale // still, some exchanges report number of open orders together with balance // if you set the following flag to 'true' ccxt will leave 'used' funds undefined in case of discrepancy 'dontGetUsedBalanceFromStaleCache': false, 'commonCurrencies': { // gets extended/overwritten in subclasses 'XBT': 'BTC', 'BCC': 'BCH', 'DRK': 'DASH', 'BCHABC': 'BCH', 'BCHSV': 'BSV', }, 'precisionMode': DECIMAL_PLACES, 'paddingMode': NO_PADDING, 'limits': { 'amount': { 'min': undefined, 'max': undefined }, 'price': { 'min': undefined, 'max': undefined }, 'cost': { 'min': undefined, 'max': undefined }, }, } // return } // describe () constructor (userConfig = {}) { Object.assign (this, functions) // if (isNode) { // this.nodeVersion = process.version.match (/\d+\.\d+\.\d+/)[0] // this.userAgent = { // 'User-Agent': 'ccxt/' + Exchange.ccxtVersion + // ' (+https://github.com/ccxt/ccxt)' + // ' Node.js/' + this.nodeVersion + ' (JavaScript)' // } // } this.options = {} // exchange-specific options, if any // fetch implementation options (JS only) this.fetchOptions = { // keepalive: true, // does not work in Chrome, https://github.com/ccxt/ccxt/issues/6368 } this.userAgents = { 'chrome': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', 'chrome39': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36', } this.headers = {} // prepended to URL, like https://proxy.com/https://exchange.com/api... this.proxy = '' this.origin = '*' // CORS origin this.minFundingAddressLength = 1 // used in checkAddress this.substituteCommonCurrencyCodes = true // reserved this.quoteJsonNumbers = true // treat numbers in json as quoted precise strings this.number = Number // or String (a pointer to a function) // whether fees should be summed by currency code this.reduceFees = true // do not delete this line, it is needed for users to be able to define their own fetchImplementation this.fetchImplementation = defaultFetch this.timeout = 10000 // milliseconds this.verbose = false this.debug = false this.userAgent = undefined this.twofa = undefined // two-factor authentication (2FA) this.apiKey = undefined this.secret = undefined this.uid = undefined this.login = undefined this.password = undefined this.privateKey = undefined // a "0x"-prefixed hexstring private key for a wallet this.walletAddress = undefined // a wallet address "0x"-prefixed hexstring this.token = undefined // reserved for HTTP auth in some cases this.balance = {} this.orderbooks = {} this.tickers = {} this.orders = undefined this.trades = {} this.transactions = {} this.ohlcvs = {} this.myTrades = undefined this.requiresWeb3 = false this.requiresEddsa = false this.precision = {} this.enableLastJsonResponse = true this.enableLastHttpResponse = true this.enableLastResponseHeaders = true this.last_http_response = undefined this.last_json_response = undefined this.last_response_headers = undefined const unCamelCaseProperties = (obj = this) => { if (obj !== null) { const ownPropertyNames = Object.getOwnPropertyNames (obj) for (let i = 0; i < ownPropertyNames.length; i++) { const k = ownPropertyNames[i] this[unCamelCase (k)] = this[k] } unCamelCaseProperties (Object.getPrototypeOf (obj)) } } unCamelCaseProperties () // merge to this const configEntries = Object.entries (this.describe ()).concat (Object.entries (userConfig)) for (let i = 0; i < configEntries.length; i++) { const [property, value] = configEntries[i] if (value && Object.getPrototypeOf (value) === Object.prototype) { this[property] = deepExtend (this[property], value) } else { this[property] = value } } const agentOptions = { 'keepAlive': true, } if (!this.httpAgent && defaultFetch.http && isNode) { this.httpAgent = new defaultFetch.http.Agent (agentOptions) } if (!this.httpsAgent && defaultFetch.https && isNode) { this.httpsAgent = new defaultFetch.https.Agent (agentOptions) } // generate old metainfo interface const hasKeys = Object.keys (this.has) for (let i = 0; i < hasKeys.length; i++) { const k = hasKeys[i] this['has' + capitalize (k)] = !!this.has[k] // converts 'emulated' to true } if (this.api) { this.defineRestApi (this.api, 'request') } this.initRestRateLimiter () if (this.markets) { this.setMarkets (this.markets) } } defaults () { return { /* override me */ } } nonce () { return this.seconds () } encodeURIComponent (...args) { return encodeURIComponent (...args) } checkRequiredCredentials (error = true) { const keys = Object.keys (this.requiredCredentials) for (let i = 0; i < keys.length; i++) { const key = keys[i] if (this.requiredCredentials[key] && !this[key]) { if (error) { throw new AuthenticationError (this.id + ' requires `' + key + '` credential') } else { return error } } } return true } checkAddress (address) { if (address === undefined) { throw new InvalidAddress (this.id + ' address is undefined') } // check the address is not the same letter like 'aaaaa' nor too short nor has a space if ((unique (address).length === 1) || address.length < this.minFundingAddressLength || address.includes (' ')) { throw new InvalidAddress (this.id + ' address is invalid or has less than ' + this.minFundingAddressLength.toString () + ' characters: "' + this.json (address) + '"') } return address } initRestRateLimiter () { if (this.rateLimit === undefined) { throw new Error (this.id + '.rateLimit property is not configured') } this.tokenBucket = this.extend ({ delay: 1, capacity: 1, defaultCost: 1, maxCapacity: 1000, }, this.tokenBucket) this.throttle = throttle (this.tokenBucket) this.executeRestRequest = (url, method = 'GET', headers = undefined, body = undefined) => { // fetchImplementation cannot be called on this. in browsers: // TypeError Failed to execute 'fetch' on 'Window': Illegal invocation const fetchImplementation = this.fetchImplementation const params = { method, headers, body, timeout: this.timeout } if (this.agent) { params['agent'] = this.agent } else if (this.httpAgent && url.indexOf ('http://') === 0) { params['agent'] = this.httpAgent } else if (this.httpsAgent && url.indexOf ('https://') === 0) { params['agent'] = this.httpsAgent } const promise = fetchImplementation (url, this.extend (params, this.fetchOptions)) .catch ((e) => { if (isNode) { throw new ExchangeNotAvailable ([ this.id, method, url, e.type, e.message ].join (' ')) } throw e // rethrow all unknown errors }) .then ((response) => this.handleRestResponse (response, url, method, headers, body)) return timeout (this.timeout, promise).catch ((e) => { if (e instanceof TimedOut) { throw new RequestTimeout (this.id + ' ' + method + ' ' + url + ' request timed out (' + this.timeout + ' ms)') } throw e }) } } setSandboxMode (enabled) { if (!!enabled) { // eslint-disable-line no-extra-boolean-cast if ('test' in this.urls) { if (typeof this.urls['api'] === 'string') { this.urls['apiBackup'] = this.urls['api'] this.urls['api'] = this.urls['test'] } else { this.urls['apiBackup'] = clone (this.urls['api']) this.urls['api'] = clone (this.urls['test']) } } else { throw new NotSupported (this.id + ' does not have a sandbox URL') } } else if ('apiBackup' in this.urls) { if (typeof this.urls['api'] === 'string') { this.urls['api'] = this.urls['apiBackup'] } else { this.urls['api'] = clone (this.urls['apiBackup']) } } } defineRestApi (api, methodName, paths = []) { const keys = Object.keys (api) for (let i = 0; i < keys.length; i++) { const key = keys[i] const value = api[key] if (Array.isArray (value)) { const uppercaseMethod = key.toUpperCase () const lowercaseMethod = key.toLowerCase () const camelcaseMethod = this.capitalize (lowercaseMethod) for (let k = 0; k < value.length; k++) { const path = value[k].trim () const splitPath = path.split (/[^a-zA-Z0-9]/) const camelcaseSuffix = splitPath.map (this.capitalize).join ('') const underscoreSuffix = splitPath.map ((x) => x.trim ().toLowerCase ()).filter ((x) => x.length > 0).join ('_') const camelcasePrefix = [ paths[0] ].concat (paths.slice (1).map (this.capitalize)).join ('') const underscorePrefix = [ paths[0] ].concat (paths.slice (1).map ((x) => x.trim ()).filter ((x) => x.length > 0)).join ('_') const camelcase = camelcasePrefix + camelcaseMethod + this.capitalize (camelcaseSuffix) const underscore = underscorePrefix + '_' + lowercaseMethod + '_' + underscoreSuffix const typeArgument = (paths.length > 1) ? paths : paths[0] const partial = async (params) => this[methodName] (path, typeArgument, uppercaseMethod, params || {}) this[camelcase] = partial this[underscore] = partial } } else { this.defineRestApi (value, methodName, paths.concat ([ key ])) } } } print (... args) { console.log (... args) } setHeaders (headers) { return headers; } fetch (url, method = 'GET', headers = undefined, body = undefined) { if (isNode && this.userAgent) { if (typeof this.userAgent === 'string') { headers = extend ({ 'User-Agent': this.userAgent }, headers) } else if ((typeof this.userAgent === 'object') && ('User-Agent' in this.userAgent)) { headers = extend (this.userAgent, headers) } } if (typeof this.proxy === 'function') { url = this.proxy (url) if (isNode) { headers = extend ({ 'Origin': this.origin }, headers) } } else if (typeof this.proxy === 'string') { if (this.proxy.length && isNode) { headers = extend ({ 'Origin': this.origin }, headers) } url = this.proxy + url } headers = extend (this.headers, headers) headers = this.setHeaders (headers) if (this.verbose) { this.print ("fetch:\n", this.id, method, url, "\nRequest:\n", headers, "\n", body, "\n") } return this.executeRestRequest (url, method, headers, body) } async fetch2 (path, type = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { if (this.enableRateLimit) { await this.throttle (this.rateLimit) } const request = this.sign (path, type, method, params, headers, body) return this.fetch (request.url, request.method, request.headers, request.body) } request (path, type = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { return this.fetch2 (path, type, method, params, headers, body) } parseJson (jsonString) { try { if (this.isJsonEncodedObject (jsonString)) { return JSON.parse (this.onJsonResponse (jsonString)) } } catch (e) { // SyntaxError return undefined } } throwExactlyMatchedException (exact, string, message) { if (string in exact) { throw new exact[string] (message) } } throwBroadlyMatchedException (broad, string, message) { const broadKey = this.findBroadlyMatchedKey (broad, string) if (broadKey !== undefined) { throw new broad[broadKey] (message) } } // a helper for matching error strings exactly vs broadly findBroadlyMatchedKey (broad, string) { const keys = Object.keys (broad) for (let i = 0; i < keys.length; i++) { const key = keys[i] if (string.indexOf (key) >= 0) { return key } } return undefined } handleErrors (statusCode, statusText, url, method, responseHeaders, responseBody, response, requestHeaders, requestBody) { // override me } handleHttpStatusCode (code, reason, url, method, body) { const codeAsString = code.toString () if (codeAsString in this.httpExceptions) { const ErrorClass = this.httpExceptions[codeAsString] throw new ErrorClass ([ this.id, method, url, code, reason, body ].join (' ')) } } getResponseHeaders (response) { const result = {} response.headers.forEach ((value, key) => { key = key.split ('-').map ((word) => capitalize (word)).join ('-') result[key] = value }) return result } handleRestResponse (response, url, method = 'GET', requestHeaders = undefined, requestBody = undefined) { return response.text ().then ((responseBody) => { const responseHeaders = this.getResponseHeaders (response) const bodyText = this.onRestResponse (response.status, response.statusText, url, method, responseHeaders, responseBody, requestHeaders, requestBody); const json = this.parseJson (bodyText) if (this.enableLastResponseHeaders) { this.last_response_headers = responseHeaders } if (this.enableLastHttpResponse) { this.last_http_response = responseBody } if (this.enableLastJsonResponse) { this.last_json_response = json } if (this.verbose) { this.print ("handleRestResponse:\n", this.id, method, url, response.status, response.statusText, "\nResponse:\n", responseHeaders, "\n", responseBody, "\n") } this.handleErrors (response.status, response.statusText, url, method, responseHeaders, responseBody, json, requestHeaders, requestBody) this.handleHttpStatusCode (response.status, response.statusText, url, method, responseBody) return json || responseBody }) } onRestResponse (statusCode, statusText, url, method, responseHeaders, responseBody, requestHeaders, requestBody) { return responseBody.trim () } onJsonResponse (responseBody) { return this.quoteJsonNumbers ? responseBody.replace (/":([+.0-9eE-]+)([,}])/g, '":"$1"$2') : responseBody; } setMarkets (markets, currencies = undefined) { const values = Object.values (markets).map ((market) => deepExtend ({ 'limits': this.limits, 'precision': this.precision, }, this.fees['trading'], market)) this.markets = indexBy (values, 'symbol') this.marketsById = indexBy (markets, 'id') this.markets_by_id = this.marketsById this.symbols = Object.keys (this.markets).sort () this.ids = Object.keys (this.markets_by_id).sort () if (currencies) { this.currencies = deepExtend (currencies, this.currencies) } else { let baseCurrencies = values.filter ((market) => 'base' in market) .map ((market) => ({ id: market.baseId || market.base, numericId: (market.baseNumericId !== undefined) ? market.baseNumericId : undefined, code: market.base, precision: market.precision ? (market.precision.base || market.precision.amount) : 8, })) let quoteCurrencies = values.filter ((market) => 'quote' in market) .map ((market) => ({ id: market.quoteId || market.quote, numericId: (market.quoteNumericId !== undefined) ? market.quoteNumericId : undefined, code: market.quote, precision: market.precision ? (market.precision.quote || market.precision.price) : 8, })) baseCurrencies = sortBy (baseCurrencies, 'code') quoteCurrencies = sortBy (quoteCurrencies, 'code') this.baseCurrencies = indexBy (baseCurrencies, 'code') this.quoteCurrencies = indexBy (quoteCurrencies, 'code') const allCurrencies = baseCurrencies.concat (quoteCurrencies) const groupedCurrencies = groupBy (allCurrencies, 'code') const currencies = Object.keys (groupedCurrencies).map ((code) => groupedCurrencies[code].reduce ((previous, current) => // eslint-disable-line implicit-arrow-linebreak ((previous.precision > current.precision) ? previous : current), groupedCurrencies[code][0])) // eslint-disable-line implicit-arrow-linebreak const sortedCurrencies = sortBy (flatten (currencies), 'code') this.currencies = deepExtend (indexBy (sortedCurrencies, 'code'), this.currencies) } this.currencies_by_id = indexBy (this.currencies, 'id') return this.markets } async loadMarketsHelper (reload = false, params = {}) { if (!reload && this.markets) { if (!this.markets_by_id) { return this.setMarkets (this.markets) } return this.markets } let currencies = undefined if (this.has.fetchCurrencies) { currencies = await this.fetchCurrencies () } const markets = await this.fetchMarkets (params) return this.setMarkets (markets, currencies) } // is async (returns a promise) loadMarkets (reload = false, params = {}) { if ((reload && !this.reloadingMarkets) || !this.marketsLoading) { this.reloadingMarkets = true this.marketsLoading = this.loadMarketsHelper (reload, params).then ((resolved) => { this.reloadingMarkets = false return resolved }, (error) => { this.reloadingMarkets = false throw error }) } return this.marketsLoading } async loadAccounts (reload = false, params = {}) { if (reload) { this.accounts = await this.fetchAccounts (params) } else { if (this.accounts) { return this.accounts } else { this.accounts = await this.fetchAccounts (params) } } this.accountsById = this.indexBy (this.accounts, 'id') return this.accounts } fetchBidsAsks (symbols = undefined, params = {}) { throw new NotSupported (this.id + ' fetchBidsAsks not supported yet') } async fetchOHLCVC (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { if (!this.has['fetchTrades']) { throw new NotSupported (this.id + ' fetchOHLCV() not supported yet') } await this.loadMarkets () const trades = await this.fetchTrades (symbol, since, limit, params) const ohlcvc = buildOHLCVC (trades, timeframe, since, limit) return ohlcvc } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { if (!this.has['fetchTrades']) { throw new NotSupported (this.id + ' fetchOHLCV() not supported yet') } await this.loadMarkets () const trades = await this.fetchTrades (symbol, since, limit, params) const ohlcvc = buildOHLCVC (trades, timeframe, since, limit) return ohlcvc.map ((c) => c.slice (0, -1)) } parseTradingViewOHLCV (ohlcvs, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { const result = this.convertTradingViewToOHLCV (ohlcvs) return this.parseOHLCVs (result, market, timeframe, since, limit) } convertTradingViewToOHLCV (ohlcvs, t = 't', o = 'o', h = 'h', l = 'l', c = 'c', v = 'v', ms = false) { const result = []; for (let i = 0; i < ohlcvs[t].length; i++) { result.push ([ ms ? ohlcvs[t][i] : (ohlcvs[t][i] * 1000), ohlcvs[o][i], ohlcvs[h][i], ohlcvs[l][i], ohlcvs[c][i], ohlcvs[v][i], ]) } return result } convertOHLCVToTradingView (ohlcvs, t = 't', o = 'o', h = 'h', l = 'l', c = 'c', v = 'v', ms = false) { const result = {} result[t] = [] result[o] = [] result[h] = [] result[l] = [] result[c] = [] result[v] = [] for (let i = 0; i < ohlcvs.length; i++) { result[t].push (ms ? ohlcvs[i][0] : parseInt (ohlcvs[i][0] / 1000)) result[o].push (ohlcvs[i][1]) result[h].push (ohlcvs[i][2]) result[l].push (ohlcvs[i][3]) result[c].push (ohlcvs[i][4]) result[v].push (ohlcvs[i][5]) } return result } fetchTicker (symbol, params = {}) { throw new NotSupported (this.id + ' fetchTicker not supported yet') } fetchTickers (symbols = undefined, params = {}) { throw new NotSupported (this.id + ' fetchTickers not supported yet') } fetchOrder (id, symbol = undefined, params = {}) { throw new NotSupported (this.id + ' fetchOrder not supported yet'); } fetchUnifiedOrder (order, params = {}) { return this.fetchOrder (this.safeValue (order, 'id'), this.safeValue (order, 'symbol'), params); } createOrder (symbol, type, side, amount, price = undefined, params = {}) { throw new NotSupported (this.id + ' createOrder not supported yet'); } cancelOrder (id, symbol = undefined, params = {}) { throw new NotSupported (this.id + ' cancelOrder not supported yet'); } cancelUnifiedOrder (order, params = {}) { return this.cancelOrder (this.safeValue (order, 'id'), this.safeValue (order, 'symbol'), params); } fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { throw new NotSupported (this.id + ' fetchOrders not supported yet'); } fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { throw new NotSupported (this.id + ' fetchOpenOrders not supported yet'); } fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { throw new NotSupported (this.id + ' fetchClosedOrders not supported yet'); } fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { throw new NotSupported (this.id + ' fetchMyTrades not supported yet'); } fetchTransactions (symbol = undefined, since = undefined, limit = undefined, params = {}) { throw new NotSupported (this.id + ' fetchTransactions not supported yet'); } fetchDeposits (symbol = undefined, since = undefined, limit = undefined, params = {}) { throw new NotSupported (this.id + ' fetchDeposits not supported yet'); } fetchWithdrawals (symbol = undefined, since = undefined, limit = undefined, params = {}) { throw new NotSupported (this.id + ' fetchWithdrawals not supported yet'); } fetchCurrencies (params = {}) { // markets are returned as a list // currencies are returned as a dict // this is for historical reasons // and may be changed for consistency later return new Promise ((resolve, reject) => resolve (this.currencies)); } fetchMarkets (params = {}) { // markets are returned as a list // currencies are returned as a dict // this is for historical reasons // and may be changed for consistency later return new Promise ((resolve, reject) => resolve (Object.values (this.markets))) } async fetchOrderStatus (id, symbol = undefined, params = {}) { const order = await this.fetchOrder (id, symbol, params); return order['status']; } account () { return { 'free': undefined, 'used': undefined, 'total': undefined, } } commonCurrencyCode (currency) { if (!this.substituteCommonCurrencyCodes) { return currency } return this.safeString (this.commonCurrencies, currency, currency) } currencyId (commonCode) { if (this.currencies === undefined) { throw new ExchangeError (this.id + ' currencies not loaded') } if (commonCode in this.currencies) { return this.currencies[commonCode]['id']; } const currencyIds = {} const distinct = Object.keys (this.commonCurrencies) for (let i = 0; i < distinct.length; i++) { const k = distinct[i] currencyIds[this.commonCurrencies[k]] = k } return this.safeString (currencyIds, commonCode, commonCode) } currency (code) { if (this.currencies === undefined) { throw new ExchangeError (this.id + ' currencies not loaded') } if ((typeof code === 'string') && (code in this.currencies)) { return this.currencies[code] } throw new ExchangeError (this.id + ' does not have currency code ' + code) } market (symbol) { if (this.markets === undefined) { throw new ExchangeError (this.id + ' markets not loaded') } if ((typeof symbol === 'string') && (symbol in this.markets)) { return this.markets[symbol] } throw new BadSymbol (this.id + ' does not have market symbol ' + symbol) } marketId (symbol) { const market = this.market (symbol) return (market !== undefined ? market['id'] : symbol) } marketIds (symbols) { return symbols.map ((symbol) => this.marketId (symbol)) } currencyIds (codes) { return codes.map ((code) => this.currencyId (code)) } symbol (symbol) { return this.market (symbol).symbol || symbol } url (path, params = {}) { let result = this.implodeParams (path, params); const query = this.omit (params, this.extractParams (path)) if (Object.keys (query).length) { result += '?' + this.urlencode (query) } return result } parseBidAsk (bidask, priceKey = 0, amountKey = 1) { const price = this.safeNumber (bidask, priceKey) const amount = this.safeNumber (bidask, amountKey) return [ price, amount ] } parseBidsAsks (bidasks, priceKey = 0, amountKey = 1) { return Object.values (bidasks || []).map ((bidask) => this.parseBidAsk (bidask, priceKey, amountKey)) } async fetchL2OrderBook (symbol, limit = undefined, params = {}) { const orderbook = await this.fetchOrderBook (symbol, limit, params) return extend (orderbook, { 'bids': sortBy (aggregate (orderbook.bids), 0, true), 'asks': sortBy (aggregate (orderbook.asks), 0), }) } parseOrderBook (orderbook, symbol, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1) { return { 'symbol': symbol, 'bids': sortBy ((bidsKey in orderbook) ? this.parseBidsAsks (orderbook[bidsKey], priceKey, amountKey) : [], 0, true), 'asks': sortBy ((asksKey in orderbook) ? this.parseBidsAsks (orderbook[asksKey], priceKey, amountKey) : [], 0), 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'nonce': undefined, } } parseBalance (balance, legacy = true) { const codes = Object.keys (this.omit (balance, [ 'info', 'timestamp', 'datetime', 'free', 'used', 'total' ])); balance['free'] = {} balance['used'] = {} balance['total'] = {} for (let i = 0; i < codes.length; i++) { const code = codes[i] if (balance[code].total === undefined) { if (balance[code].free !== undefined && balance[code].used !== undefined) { if (legacy) { balance[code].total = this.sum (balance[code].free, balance[code].used) } else { balance[code].total = Precise.stringAdd (balance[code].free, balance[code].used) } } } if (balance[code].free === undefined) { if (balance[code].total !== undefined && balance[code].used !== undefined) { if (legacy) { balance[code].free = this.sum (balance[code].total, -balance[code].used) } else { balance[code].free = Precise.stringSub (balance[code].total, balance[code].used) } } } if (balance[code].used === undefined) { if (balance[code].total !== undefined && balance[code].free !== undefined) { if (legacy) { balance[code].used = this.sum (balance[code].total, -balance[code].free) } else { balance[code].used = Precise.stringSub (balance[code].total, balance[code].free) } } } balance[code].free = this.parseNumber (balance[code].free) balance[code].used = this.parseNumber (balance[code].used) balance[code].total = this.parseNumber (balance[code].total) balance.free[code] = balance[code].free balance.used[code] = balance[code].used balance.total[code] = balance[code].total } return balance } async fetchBalance (params = {}) { throw new NotSupported (this.id + ' fetchBalance not supported yet') } async fetchPartialBalance (part, params = {}) { const balance = await this.fetchBalance (params) return balance[part] } fetchFreeBalance (params = {}) { return this.fetchPartialBalance ('free', params) } fetchUsedBalance (params = {}) { return this.fetchPartialBalance ('used', params) } fetchTotalBalance (params = {}) { return this.fetchPartialBalance ('total', params) } async fetchStatus (params = {}) { if (this.has['fetchTime']) { const time = await this.fetchTime (params) this.status = this.extend (this.status, { 'updated': time, }) } return this.status } async fetchTradingFees (params = {}) { throw new NotSupported (this.id + ' fetchTradingFees not supported yet') } async fetchTradingFee (symbol, params = {}) { if (!this.has['fetchTradingFees']) { throw new NotSupported (this.id + ' fetchTradingFee not supported yet') } return await this.fetchTradingFees (params) } async loadTradingLimits (symbols = undefined, reload = false, params = {}) { if (this.has['fetchTradingLimits']) { if (reload || !('limitsLoaded' in this.options)) { const response = await this.fetchTradingLimits (symbols); for (let i = 0; i < symbols.length; i++) { const symbol = symbols[i]; this.markets[symbol] = this.deepExtend (this.markets[symbol], response[symbol]); } this.options['limitsLoaded'] = this.milliseconds (); } } return this.markets; } filterBySinceLimit (array, since = undefined, limit = undefined, key = 'timestamp', tail = false) { const sinceIsDefined = (since !== undefined && since !== null) if (sinceIsDefined) { array = array.filter ((entry) => entry[key] >= since) } if (limit !== undefined && limit !== null) { array = tail ? array.slice (-limit) : array.slice (0, limit) } return array } filterByValueSinceLimit (array, field, value = undefined, since = undefined, limit = undefined, key = 'timestamp', tail = false) { const valueIsDefined = value !== undefined && value !== null const sinceIsDefined = since !== undefined && since !== null // single-pass filter for both symbol and since if (valueIsDefined || sinceIsDefined) { array = array.filter ((entry) => ((valueIsDefined ? (entry[field] === value) : true) && (sinceIsDefined ? (entry[key] >= since) : true))) } if (limit !== undefined && limit !== null) { array = tail ? array.slice (-limit) : array.slice (0, limit) } return array } filterBySymbolSinceLimit (array, symbol = undefined, since = undefined, limit = undefined, tail = false) { return this.filterByValueSinceLimit (array, 'symbol', symbol, since, limit, 'timestamp', tail) } filterByCurrencySinceLimit (array, code = undefined, since = undefined, limit = undefined, tail = false) { return this.filterByValueSinceLimit (array, 'currency', code, since, limit, 'timestamp', tail) } filterByArray (objects, key, values = undefined, indexed = true) { objects = Object.values (objects) // return all of them if no values were passed if (values === undefined || values === null) { return indexed ? indexBy (objects, key) : objects } const result = [] for (let i = 0; i < objects.length; i++) { if (values.includes (objects[i][key])) { result.push (objects[i]) } } return indexed ? indexBy (result, key) : result } safeTicker (ticker, market = undefined) { const symbol = this.safeValue (ticker, 'symbol'); if (symbol === undefined) { ticker['symbol'] = this.safeSymbol (undefined, market); } const timestamp = this.safeInteger (ticker, 'timestamp'); if (timestamp !== undefined) { ticker['timestamp'] = timestamp; ticker['datetime'] = this.iso8601 (timestamp); } const baseVolume = this.safeValue (ticker, 'baseVolume'); const quoteVolume = this.safeValue (ticker, 'quoteVolume'); const vwap = this.safeValue (ticker, 'vwap'); if (vwap === undefined) { ticker['vwap'] = this.vwap (baseVolume, quoteVolume); } const close = this.safeValue (ticker, 'close'); const last = this.safeValue (ticker, 'last'); if ((close === undefined) && (last !== undefined)) { ticker['close'] = last; } else if ((last === undefined) && (close !== undefined)) { ticker['last'] = close; } return ticker; } parseTickers (tickers, symbols = undefined, params = {}) { const result = []; const values = Object.values (tickers || []); for (let i = 0; i < values.length; i++) { result.push (this.extend (this.parseTicker (values[i]), params)); } return this.filterByArray (result, 'symbol', symbols); } parseDepositAddresses (addresses, codes = undefined) { let result = []; for (let i = 0; i < addresses.length; i++) { const address = this.parseDepositAddress (addresses[i]); result.push (address); } if (codes) { result = this.filterByArray (result, 'currency', codes); } return this.indexBy (result, 'currency'); } parseTrades (trades, market = undefined, since = undefined, limit = undefined, params = {}) { let result = Object.values (trades || []).map ((trade) => this.extend (this.parseTrade (trade, market), params)) result = sortBy (result, 'timestamp') const symbol = (market !== undefined) ? market['symbol'] : undefined const tail = since === undefined return this.filterBySymbolSinceLimit (result, symbol, since, limit, tail) } parseTransactions (transactions, currency = undefined, since = undefined, limit = undefined, params = {}) { let result = Object.values (transactions || []).map ((transaction) => this.extend (this.parseTransaction (transaction, currency), params)) result = this.sortBy (result, 'timestamp'); const code = (currency !== undefined) ? currency['code'] : undefined; const tail = since === undefined; return this.filterByCurrencySinceLimit (result, code, since, limit, tail); } parseTransfers (transfers, currency = undefined, since = undefined, limit = undefined, params = {}) { let result = Object.values (transfers || []).map ((transfer) => this.extend (this.parseTransfer (transfer, currency), params)) result = this.sortBy (result, 'timestamp'); const code = (currency !== undefined) ? currency['code'] : undefined; const tail = since === undefined; return this.filterByCurrencySinceLimit (result, code, since, limit, tail); } parseLedger (data, currency = undefined, since = undefined, limit = undefined, params = {}) { let result = []; const array = Object.values (data || []); for (let i = 0; i < array.length; i++) { const itemOrItems = this.parseLedgerEntry (array[i], currency); if (Array.isArray (itemOrItems)) { for (let j = 0; j < itemOrItems.length; j++) { result.push (this.extend (itemOrItems[j], params)); } } else { result.push (this.extend (itemOrItems, params)); } } result = this.sortBy (result, 'timestamp'); const code = (currency !== undefined) ? currency['code'] : undefined; const tail = since === undefined; return this.filterByCurrencySinceLimit (result, code, since, limit, tail); } parseOrders (orders, market = undefined, since = undefined, limit = undefined, params = {}) { // // the value of orders is either a dict or a list // // dict // // { // 'id1': { ... }, // 'id2': { ... }, // 'id3': { ... }, // ... // } // // list // // [ // { 'id': 'id1', ... }, // { 'id': 'id2', ... }, // { 'id': 'id3', ... }, // ... // ] // let result = Array.isArray (orders) ? Object.values (orders).map ((order) => this.extend (this.parseOrder (order, market), params)) : Object.entries (orders).map (([ id, order ]) => this.extend (this.parseOrder (this.extend ({ 'id': id }, order), market), params)) result = sortBy (result, 'timestamp') const symbol = (market !== undefined) ? market['symbol'] : undefined const tail = since === undefined return this.filterBySymbolSinceLimit (result, symbol, since, limit, tail) } safeCurrency (currencyId, currency = undefined) { if ((currencyId === undefined) && (currency !== undefined)) { return currency } if ((this.currencies_by_id !== undefined) && (currencyId in this.currencies_by_id)) { return this.currencies_by_id[currencyId] } return { 'id': currencyId, 'code': (currencyId === undefined) ? currencyId : this.commonCurrencyCode (currencyId.toUpperCase ()), } } safeCurrencyCode (currencyId, currency = undefined) { currency = this.safeCurrency (currencyId, currency) return currency['code'] } safeMarket (marketId, market = undefined, delimiter = undefined) { if (marketId !== undefined) { if (this.markets_by_id !== undefined && marketId in this.markets_by_id) { market = this.markets_by_id[marketId] } else if (delimiter !== undefined) { const [ baseId, quoteId ] = marketId.split (delimiter) const base = this.safeCurrencyCode (baseId) const quote = this.safeCurrencyCode (quoteId) const symbol = base + '/' + quote return { 'id': marketId, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, } } } if (market !== undefined) { return market } return { 'id': marketId, 'symbol': marketId, 'base': undefined, 'quote': undefined, 'baseId': undefined, 'quoteId': undefined, } } safeSymbol (marketId, market = undefined, delimiter = undefined) { market = this.safeMarket (marketId, market, delimiter) return market['symbol']; } filterBySymbol (array, symbol = undefined) { return ((symbol !== undefined) ? array.filter ((entry) => entry.symbol === symbol) : array) } parseOHLCV (ohlcv, market = undefined) { return Array.isArray (ohlcv) ? ohlcv.slice (0, 6) : ohlcv