UNPKG

aspernaturet

Version:

A library for cryptocurrency trading and e-commerce with support for many bitcoin/ether/altcoin exchange markets and merchant APIs

1,303 lines (1,146 loc) 609 kB
"use strict"; /* MIT License Copyright (c) 2017 Igor Kroitor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function () { //----------------------------------------------------------------------------- // dependencies const CryptoJS = require ('crypto-js') , qs = require ('qs') // querystring // , ws = require ('ws') // websocket //----------------------------------------------------------------------------- // this is updated by vss.js when building const version = '1.5.110' //----------------------------------------------------------------------------- // platform detection const isNode = (typeof window === 'undefined') , isCommonJS = (typeof module !== 'undefined') && (typeof require !== 'undefined') //----------------------------------------------------------------------------- class CCXTError extends Error { constructor (message) { super (message) // a workaround to make `instanceof CCXTError` work in ES5 this.constructor = CCXTError this.__proto__ = CCXTError.prototype this.message = message } } class ExchangeError extends CCXTError { constructor (message) { super (message) this.constructor = ExchangeError this.__proto__ = ExchangeError.prototype this.message = message } } class NotSupported extends ExchangeError { constructor (message) { super (message) this.constructor = NotSupported this.__proto__ = NotSupported.prototype this.message = message } } class AuthenticationError extends ExchangeError { constructor (message) { super (message) this.constructor = AuthenticationError this.__proto__ = AuthenticationError.prototype this.message = message } } class InsufficientFunds extends ExchangeError { constructor (message) { super (message) this.constructor = InsufficientFunds this.__proto__ = InsufficientFunds.prototype this.message = message } } class NetworkError extends CCXTError { constructor (message) { super (message) this.constructor = NetworkError this.__proto__ = NetworkError.prototype this.message = message } } class DDoSProtection extends NetworkError { constructor (message) { super (message) this.constructor = DDoSProtection this.__proto__ = DDoSProtection.prototype this.message = message } } class RequestTimeout extends NetworkError { constructor (message) { super (message) this.constructor = RequestTimeout this.__proto__ = RequestTimeout.prototype this.message = message } } class ExchangeNotAvailable extends NetworkError { constructor (message) { super (message) this.constructor = ExchangeNotAvailable this.__proto__ = ExchangeNotAvailable.prototype this.message = message } } //----------------------------------------------------------------------------- // utility helpers const sleep = ms => new Promise (resolve => setTimeout (resolve, ms)); const decimal = float => parseFloat (float).toString () const timeout = (ms, promise) => Promise.race ([ promise, sleep (ms).then (() => { throw new RequestTimeout ('request timed out') }) ]) const capitalize = string => string.length ? (string.charAt (0).toUpperCase () + string.slice (1)) : string const keysort = object => { const result = {} Object.keys (object).sort ().forEach (key => result[key] = object[key]) return result } const extend = (...args) => { const result = {} for (let i = 0; i < args.length; i++) if (typeof args[i] === 'object') Object.keys (args[i]).forEach (key => (result[key] = args[i][key])) return result } const omit = function (object) { const result = extend (object) for (let i = 1; i < arguments.length; i++) if (typeof arguments[i] === 'string') delete result[arguments[i]] else if (Array.isArray (arguments[i])) for (var k = 0; k < arguments[i].length; k++) delete result[arguments[i][k]] return result } const indexBy = (array, key) => { const result = {} for (var i = 0; i < array.length; i++) { let element = array[i] if (typeof element[key] != 'undefined') { result[element[key]] = element } } return result } const sortBy = (array, key, descending = false) => { descending = descending ? -1 : 1 return array.sort ((a, b) => ((a[key] < b[key]) ? -descending : ((a[key] > b[key]) ? descending : 0))) } const flatten = (array, result = []) => { for (let i = 0, length = array.length; i < length; i++) { const value = array[i] if (Array.isArray (value)) { flatten (value, result) } else { result.push (value) } } return result } const unique = array => array.filter ((value, index, self) => (self.indexOf (value) == index)) const pluck = (array, key) => array .filter (element => (typeof element[key] != 'undefined')) .map (element => element[key]) const urlencode = object => qs.stringify (object) const sum = (...args) => { const result = args.filter (arg => typeof arg != 'undefined') return (result.length > 0) ? result.reduce ((sum, value) => sum + value, 0) : undefined } const ordered = x => x // a stub to keep assoc keys in order, in JS it does nothing, it's mostly for Python //----------------------------------------------------------------------------- // a cross-platform Fetch API const nodeFetch = isNode && module.require ('node-fetch') // using module.require to prevent Webpack / React Native from trying to include it , windowFetch = (typeof window !== 'undefined' && window.fetch) // native Fetch API (in newer browsers) , xhrFetch = (url, options, verbose = false) => // a quick ad-hoc polyfill (for older browsers) new Promise ((resolve, reject) => { if (verbose) console.log (url, options) const xhr = new XMLHttpRequest () const method = options.method || 'GET' xhr.open (method, url, true) xhr.onreadystatechange = () => { if (xhr.readyState == 4) { if (xhr.status == 200) resolve (xhr.responseText) else { // [403, 404, ...].indexOf (xhr.status) >= 0 throw new Error (method, url, xhr.status, xhr.responseText) } } } if (typeof options.headers != 'undefined') for (var header in options.headers) xhr.setRequestHeader (header, options.headers[header]) xhr.send (options.body) }) const fetch = nodeFetch || windowFetch || xhrFetch //----------------------------------------------------------------------------- // string ←→ binary ←→ base64 conversion routines const stringToBinary = str => { const arr = new Uint8Array (str.length) for (let i = 0; i < str.length; i++) { arr[i] = str.charCodeAt(i); } return CryptoJS.lib.WordArray.create (arr) } const stringToBase64 = string => CryptoJS.enc.Latin1.parse (string).toString (CryptoJS.enc.Base64) , utf16ToBase64 = string => CryptoJS.enc.Utf16 .parse (string).toString (CryptoJS.enc.Base64) , base64ToBinary = string => CryptoJS.enc.Base64.parse (string) , base64ToString = string => CryptoJS.enc.Base64.parse (string).toString (CryptoJS.enc.Utf8) , binaryToString = string => string const binaryConcat = (...args) => args.reduce ((a, b) => a.concat (b)) // url-safe-base64 without equals signs, with + replaced by - and slashes replaced by underscores const urlencodeBase64 = base64string => base64string.replace (/[=]+$/, '') .replace (/\+/g, '-') .replace (/\//g, '_') //----------------------------------------------------------------------------- // cryptography const hash = (request, hash = 'md5', digest = 'hex') => { const result = CryptoJS[hash.toUpperCase ()] (request) return (digest == 'binary') ? result : result.toString (CryptoJS.enc[capitalize (digest)]) } const hmac = (request, secret, hash = 'sha256', digest = 'hex') => { const encoding = (digest == 'binary') ? 'Latin1' : capitalize (digest) return CryptoJS['Hmac' + hash.toUpperCase ()] (request, secret).toString (CryptoJS.enc[capitalize (encoding)]) } //----------------------------------------------------------------------------- // a JSON Web Token authentication method const jwt = (request, secret, alg = 'HS256', hash = 'sha256') => { const encodedHeader = urlencodeBase64 (stringToBase64 (JSON.stringify ({ 'alg': alg, 'typ': 'JWT' }))) , encodedData = urlencodeBase64 (stringToBase64 (JSON.stringify (request))) , token = [ encodedHeader, encodedData ].join ('.') , signature = urlencodeBase64 (utf16ToBase64 (hmac (token, secret, hash, 'utf16'))) return [ token, signature ].join ('.') } //----------------------------------------------------------------------------- // const WebSocket = require('ws') // const ws = new WebSocket (this.urls['websocket']) // ws.on ('open', function open () { // console.log ('connected') // // ws.send (Date.now ()) // }) // ws.on ('close', function close () { // console.log ('disconnected') // }); // ws.on ('message', function incoming (data) { // // console.log (`Roundtrip time: ${Date.now() - data} ms`); // setTimeout (function timeout () { // ws.send (Date.now ()) // }, 500) // }) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // the base class const Exchange = function (config) { this.hash = hash this.hmac = hmac this.jwt = jwt // JSON Web Token this.binaryConcat = binaryConcat this.stringToBinary = stringToBinary this.stringToBase64 = stringToBase64 this.base64ToBinary = base64ToBinary this.base64ToString = base64ToString this.binaryToString = binaryToString this.utf16ToBase64 = utf16ToBase64 this.urlencode = urlencode this.encodeURIComponent = encodeURIComponent this.omit = omit this.pluck = pluck this.unique = unique this.extend = extend this.flatten = flatten this.indexBy = indexBy this.sortBy = sortBy this.keysort = keysort this.decimal = decimal this.capitalize = capitalize this.json = JSON.stringify this.sum = sum this.ordered = ordered this.encode = string => string this.decode = string => string if (isNode) this.nodeVersion = process.version.match (/\d+\.\d+.\d+/) [0] this.init = function () { this.orders = {} this.trades = {} if (this.api) this.defineRESTAPI (this.api, 'request'); if (this.markets) this.setMarkets (this.markets); } this.defineRESTAPI = function (api, methodName, options = {}) { Object.keys (api).forEach (type => { Object.keys (api[type]).forEach (httpMethod => { let urls = api[type][httpMethod] for (let i = 0; i < urls.length; i++) { let url = urls[i].trim () let splitPath = url.split (/[^a-zA-Z0-9]/) let uppercaseMethod = httpMethod.toUpperCase () let lowercaseMethod = httpMethod.toLowerCase () let camelcaseMethod = capitalize (lowercaseMethod) let camelcaseSuffix = splitPath.map (capitalize).join ('') let underscoreSuffix = splitPath.map (x => x.trim ().toLowerCase ()).filter (x => x.length > 0).join ('_') if (camelcaseSuffix.indexOf (camelcaseMethod) === 0) camelcaseSuffix = camelcaseSuffix.slice (camelcaseMethod.length) if (underscoreSuffix.indexOf (lowercaseMethod) === 0) underscoreSuffix = underscoreSuffix.slice (lowercaseMethod.length) let camelcase = type + camelcaseMethod + capitalize (camelcaseSuffix) let underscore = type + '_' + lowercaseMethod + '_' + underscoreSuffix if ('suffixes' in options) { if ('camelcase' in options['suffixes']) camelcase += options['suffixes']['camelcase'] if ('underscore' in options.suffixes) underscore += options['suffixes']['underscore'] } if ('underscore_suffix' in options) underscore += options.underscoreSuffix; if ('camelcase_suffix' in options) camelcase += options.camelcaseSuffix; let partial = async params => this[methodName] (url, type, uppercaseMethod, params) this[camelcase] = partial this[underscore] = partial } }) }) }, // this.initializeStreamingAPI = function () { // this.ws = new WebSocket (this.urls['websocket']) // ws.on ('open', function open () { // console.log ('connected') // // ws.send (Date.now ()) // }) // ws.on ('close', function close () { // console.log ('disconnected') // }) // ws.on ('message', function incoming (data) { // // console.log (`Roundtrip time: ${Date.now() - data} ms`); // setTimeout (function timeout () { // ws.send (Date.now ()) // }, 500) // }) // }, this.fetch = function (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 (this.proxy.length) headers = extend ({ 'Origin': '*' }, headers) let options = { 'method': method, 'headers': headers, 'body': body } url = this.proxy + url if (this.verbose) console.log (this.id, method, url, "\nRequest:\n", options) return timeout (this.timeout, fetch (url, options) .catch (e => { if (isNode) { throw new ExchangeNotAvailable ([ this.id, method, url, e.type, e.message ].join (' ')) } throw e // rethrow all unknown errors }) .then (response => { if (typeof response == 'string') return response return response.text ().then (text => { if (this.verbose) console.log (this.id, method, url, text ? ("\nResponse:\n" + text) : '') if ((response.status >= 200) && (response.status <= 300)) return text let error = undefined let details = text if ([ 429 ].indexOf (response.status) >= 0) { error = DDoSProtection } else if ([ 404, 409, 422, 500, 501, 502, 520, 521, 522, 525 ].indexOf (response.status) >= 0) { error = ExchangeNotAvailable } else if ([ 400, 403, 405, 503 ].indexOf (response.status) >= 0) { let ddosProtection = text.match (/cloudflare|incapsula/i) if (ddosProtection) { error = DDoSProtection } else { error = ExchangeNotAvailable details = text + ' (possible reasons: ' + [ 'invalid API keys', 'bad or old nonce', 'exchange is down or offline', 'on maintenance', 'DDoS protection', 'rate-limiting', ].join (', ') + ')' } } else if ([ 408, 504 ].indexOf (response.status) >= 0) { error = RequestTimeout } else if ([ 401, 511 ].indexOf (response.status) >= 0) { error = AuthenticationError } else { error = ExchangeError } throw new error ([ this.id, method, url, response.status, response.statusText, details ].join (' ')) }) }).then (response => this.handleResponse (url, method, headers, response))) } this.handleResponse = function (url, method = 'GET', headers = undefined, body = undefined) { try { if ((typeof body != 'string') || (body.length < 2)) throw new ExchangeError ([this.id, method, url, 'returned empty response'].join (' ')) return JSON.parse (body) } catch (e) { let maintenance = body.match (/offline|busy|retry|wait|unavailable|maintain|maintenance|maintenancing/i) let ddosProtection = body.match (/cloudflare|incapsula|overload/i) if (e instanceof SyntaxError) { let error = ExchangeNotAvailable let details = 'not accessible from this location at the moment' if (maintenance) details = 'offline, on maintenance or unreachable from this location at the moment' if (ddosProtection) error = DDoSProtection throw new error ([ this.id, method, url, details ].join (' ')) } if (this.verbose) console.log (this.id, method, url, 'error', e, "response body:\n'" + body + "'") throw e } } this.set_markets = this.setMarkets = function (markets) { let values = Object.values (markets) 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 () let base = this.pluck (values.filter (market => 'base' in market), 'base') let quote = this.pluck (values.filter (market => 'quote' in market), 'quote') this.currencies = this.unique (base.concat (quote)) return this.markets } this.load_markets = this.loadMarkets = function (reload = false) { if (!reload && this.markets) { if (!this.marketsById) { return new Promise ((resolve, reject) => resolve (this.setMarkets (this.markets))) } return new Promise ((resolve, reject) => resolve (this.markets)) } return this.fetchMarkets ().then (markets => { return this.setMarkets (markets) }) } this.fetch_tickers = function (symbols = undefined) { return this.fetchTickers (symbols) } this.fetchTickers = function (symbols = undefined) { throw new NotSupported (this.id + ' API does not allow to fetch all tickers at once with a single call to fetch_tickers () for now') } this.fetch_markets = function () { return this.fetchMarkets () } this.fetchMarkets = function () { return new Promise ((resolve, reject) => resolve (this.markets)) } this.fetchOrderStatus = async function (id, market = undefined) { let order = await fetchOrder (id) return order['status'] } this.account = function () { return { 'free': 0.0, 'used': 0.0, 'total': 0.0, } } this.commonCurrencyCode = function (currency) { if (!this.substituteCommonCurrencyCodes) return currency if (currency == 'XBT') return 'BTC' if (currency == 'BCC') return 'BCH' if (currency == 'DRK') return 'DASH' return currency } this.market = function (symbol) { return (((typeof symbol === 'string') && (typeof this.markets != 'undefined') && (typeof this.markets[symbol] != 'undefined')) ? this.markets[symbol] : symbol) } this.market_id = this.marketId = function (symbol) { return this.market (symbol).id || symbol } this.market_ids = this.marketIds = function (symbols) { return symbols.map (symbol => this.marketId(symbol)); } this.symbol = function (symbol) { return this.market (symbol).symbol || symbol } this.extract_params = this.extractParams = function (string) { var re = /{([a-zA-Z0-9_]+?)}/g var matches = [] let match while (match = re.exec (string)) matches.push (match[1]) return matches } this.implode_params = this.implodeParams = function (string, params) { for (var property in params) string = string.replace ('{' + property + '}', params[property]) return string } this.url = function (path, params = {}) { let result = this.implodeParams (path, params); let query = this.omit (params, this.extractParams (path)) if (Object.keys (query).length) result += '?' + this.urlencode (query) return result } this.parseTrades = function (trades, market = undefined) { let result = [] for (let t = 0; t < trades.length; t++) { result.push (this.parseTrade (trades[t], market)) } return result } this.parseOrders = function (orders, market = undefined) { let result = [] for (let t = 0; t < orders.length; t++) { result.push (this.parseOrder (orders[t], market)) } return result } this.parseOHLCV = function (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { return ohlcv } this.parseOHLCVs = function (ohlcvs, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { let result = [] for (let t = 0; t < ohlcvs.length; t++) { result.push (this.parseOHLCV (ohlcvs[t], market, timeframe, since, limit)) } return result } this.createLimitBuyOrder = function (market, amount, price, params = {}) { return this.createOrder (market, 'limit', 'buy', amount, price, params) } this.createLimitSellOrder = function (market, amount, price, params = {}) { return this.createOrder (market, 'limit', 'sell', amount, price, params) } this.createMarketBuyOrder = function (market, amount, params = {}) { return this.createOrder (market, 'market', 'buy', amount, undefined, params) } this.createMarketSellOrder = function (market, amount, params = {}) { return this.createOrder (market, 'market', 'sell', amount, undefined, params) } this.iso8601 = timestamp => new Date (timestamp).toISOString () this.parse8601 = Date.parse this.seconds = () => Math.floor (this.milliseconds () / 1000) this.microseconds = () => Math.floor (this.milliseconds () * 1000) this.milliseconds = Date.now this.nonce = this.seconds this.id = undefined this.rateLimit = 2000 // milliseconds = seconds * 1000 this.timeout = 10000 // milliseconds = seconds * 1000 this.verbose = false this.userAgent = false this.twofa = false // two-factor authentication this.substituteCommonCurrencyCodes = true this.timeFrames = undefined this.hasFetchOHLCV = false this.yyyymmddhhmmss = timestamp => { let date = new Date (timestamp) let yyyy = date.getUTCFullYear () let MM = date.getUTCMonth () let dd = date.getUTCDay () let hh = date.getUTCHours () let mm = date.getUTCMinutes () let ss = date.getUTCSeconds () MM = MM < 10 ? ('0' + MM) : MM dd = dd < 10 ? ('0' + dd) : dd hh = hh < 10 ? ('0' + hh) : hh mm = mm < 10 ? ('0' + mm) : mm ss = ss < 10 ? ('0' + ss) : ss return yyyy + '-' + MM + '-' + dd + ' ' + hh + ':' + mm + ':' + ss } if (isNode) this.userAgent = { 'User-Agent': 'ccxt/' + version + ' (+https://github.com/kroitor/ccxt)' + ' Node.js/' + this.nodeVersion + ' (JavaScript)' } // prepended to URL, like https://proxy.com/https://exchange.com/api... this.proxy = '' this.hasFetchTickers = false this.hasFetchOHLCV = false for (var property in config) this[property] = config[property] this.account = this.account this.fetch_balance = this.fetchBalance this.fetch_order_book = this.fetchOrderBook this.fetch_ticker = this.fetchTicker this.fetch_trades = this.fetchTrades this.fetch_order = this.fetchOrder this.fetch_order_status = this.fetchOrderStatus this.parse_order_book = this.parseOrderBook this.parse_trades = this.parseTrades this.parse_orders = this.parseOrders this.parse_ohlcv = this.parseOHLCV this.parse_ohlcvs = this.parseOHLCVs this.create_limit_buy_order = this.createLimitBuyOrder this.create_limit_sell_order = this.createLimitBuyOrder this.create_market_buy_order = this.createLimitBuyOrder this.create_market_sell_order = this.createLimitBuyOrder this.create_order = this.createOrder this.init () } //============================================================================= var _1broker = { 'id': '_1broker', 'name': '1Broker', 'countries': 'US', 'rateLimit': 1500, 'version': 'v2', 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27766021-420bd9fc-5ecb-11e7-8ed6-56d0081efed2.jpg', 'api': 'https://1broker.com/api', 'www': 'https://1broker.com', 'doc': 'https://1broker.com/?c=en/content/api-documentation', }, 'api': { 'private': { 'get': [ 'market/bars', 'market/categories', 'market/details', 'market/list', 'market/quotes', 'market/ticks', 'order/cancel', 'order/create', 'order/open', 'position/close', 'position/close_cancel', 'position/edit', 'position/history', 'position/open', 'position/shared/get', 'social/profile_statistics', 'social/profile_trades', 'user/bitcoin_deposit_address', 'user/details', 'user/overview', 'user/quota_status', 'user/transaction_log', ], }, }, async fetchCategories () { let categories = await this.privateGetMarketCategories (); return categories['response']; }, async fetchMarkets () { let this_ = this; // workaround for Babel bug (not passing `this` to _recursive() call) let categories = await this.fetchCategories (); let result = []; for (let c = 0; c < categories.length; c++) { let category = categories[c]; let markets = await this_.privateGetMarketList ({ 'category': category.toLowerCase (), }); for (let p = 0; p < markets['response'].length; p++) { let market = markets['response'][p]; let id = market['symbol']; let symbol = undefined; let base = undefined; let quote = undefined; if ((category == 'FOREX') || (category == 'CRYPTO')) { symbol = market['name']; let parts = symbol.split ('/'); base = parts[0]; quote = parts[1]; } else { base = id; quote = 'USD'; symbol = base + '/' + quote; } base = this_.commonCurrencyCode (base); quote = this_.commonCurrencyCode (quote); result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'info': market, }); } } return result; }, async fetchBalance (params = {}) { await this.loadMarkets (); let balance = await this.privateGetUserOverview (); let response = balance['response']; let result = { 'info': response, }; for (let c = 0; c < this.currencies.length; c++) { let currency = this.currencies[c]; result[currency] = this.account (); } let total = parseFloat (response['balance']); result['BTC']['free'] = total; result['BTC']['total'] = total; return result; }, async fetchOrderBook (market, params = {}) { await this.loadMarkets (); let response = await this.privateGetMarketQuotes (this.extend ({ 'symbols': this.marketId (market), }, params)); let orderbook = response['response'][0]; let timestamp = this.parse8601 (orderbook['updated']); let bidPrice = parseFloat (orderbook['bid']); let askPrice = parseFloat (orderbook['ask']); let bid = [ bidPrice, undefined ]; let ask = [ askPrice, undefined ]; return { 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'bids': [ bid ], 'asks': [ ask ], }; }, async fetchTrades (market) { throw new ExchangeError (this.id + ' fetchTrades () method not implemented yet'); }, async fetchTicker (market) { await this.loadMarkets (); let result = await this.privateGetMarketBars ({ 'symbol': this.marketId (market), 'resolution': 60, 'limit': 1, }); let orderbook = await this.fetchOrderBook (market); let ticker = result['response'][0]; let timestamp = this.parse8601 (ticker['date']); return { 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': parseFloat (ticker['h']), 'low': parseFloat (ticker['l']), 'bid': orderbook['bids'][0][0], 'ask': orderbook['asks'][0][0], 'vwap': undefined, 'open': parseFloat (ticker['o']), 'close': parseFloat (ticker['c']), 'first': undefined, 'last': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': undefined, 'quoteVolume': undefined, }; }, async createOrder (market, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); let order = { 'symbol': this.marketId (market), 'margin': amount, 'direction': (side == 'sell') ? 'short' : 'long', 'leverage': 1, 'type': side, }; if (type == 'limit') order['price'] = price; else order['type'] += '_market'; let result = await this.privateGetOrderCreate (this.extend (order, params)); return { 'info': result, 'id': result['response']['order_id'], }; }, async cancelOrder (id) { await this.loadMarkets (); return this.privatePostOrderCancel ({ 'order_id': id }); }, async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { if (!this.apiKey) throw new AuthenticationError (this.id + ' requires apiKey for all requests'); let url = this.urls['api'] + '/' + this.version + '/' + path + '.php'; let query = this.extend ({ 'token': this.apiKey }, params); url += '?' + this.urlencode (query); let response = await this.fetch (url, method); if ('warning' in response) if (response['warning']) throw new ExchangeError (this.id + ' Warning: ' + response['warning_message']); if ('error' in response) if (response['error']) throw new ExchangeError (this.id + ' Error: ' + response['error_code'] + response['error_message']); return response; }, } //----------------------------------------------------------------------------- var cryptocapital = { 'id': 'cryptocapital', 'name': 'Crypto Capital', 'comment': 'Crypto Capital API', 'countries': 'PA', // Panama 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27993158-7a13f140-64ac-11e7-89cc-a3b441f0b0f8.jpg', 'www': 'https://cryptocapital.co', 'doc': 'https://github.com/cryptocap', }, 'api': { 'public': { 'get': [ 'stats', 'historical-prices', 'order-book', 'transactions', ], }, 'private': { 'post': [ 'balances-and-info', 'open-orders', 'user-transactions', 'btc-deposit-address/get', 'btc-deposit-address/new', 'deposits/get', 'withdrawals/get', 'orders/new', 'orders/edit', 'orders/cancel', 'orders/status', 'withdrawals/new', ], }, }, async fetchBalance (params = {}) { let response = await this.privatePostBalancesAndInfo (); let balance = response['balances-and-info']; let result = { 'info': balance }; for (let c = 0; c < this.currencies.length; c++) { let currency = this.currencies[c]; let account = this.account (); if (currency in balance['available']) account['free'] = parseFloat (balance['available'][currency]); if (currency in balance['on_hold']) account['used'] = parseFloat (balance['on_hold'][currency]); account['total'] = this.sum (account['free'], account['used']); result[currency] = account; } return result; }, async fetchOrderBook (market, params = {}) { let response = await this.publicGetOrderBook (this.extend ({ 'currency': this.marketId (market), }, params)); let orderbook = response['order-book']; let timestamp = this.milliseconds (); let result = { 'bids': [], 'asks': [], 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), }; let sides = { 'bids': 'bid', 'asks': 'ask' }; let keys = Object.keys (sides); for (let k = 0; k < keys.length; k++) { let key = keys[k]; let side = sides[key]; let orders = orderbook[side]; for (let i = 0; i < orders.length; i++) { let order = orders[i]; let timestamp = parseInt (order['timestamp']) * 1000; let price = parseFloat (order['price']); let amount = parseFloat (order['order_amount']); result[key].push ([ price, amount, timestamp ]); } } return result; }, async fetchTicker (market) { let response = await this.publicGetStats ({ 'currency': this.marketId (market), }); let ticker = response['stats']; let timestamp = this.milliseconds (); return { 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': parseFloat (ticker['max']), 'low': parseFloat (ticker['min']), 'bid': parseFloat (ticker['bid']), 'ask': parseFloat (ticker['ask']), 'vwap': undefined, 'open': parseFloat (ticker['open']), 'close': undefined, 'first': undefined, 'last': parseFloat (ticker['last_price']), 'change': parseFloat (ticker['daily_change']), 'percentage': undefined, 'average': undefined, 'baseVolume': undefined, 'quoteVolume': parseFloat (ticker['total_btc_traded']), }; }, async fetchTrades (market, params = {}) { return this.publicGetTransactions (this.extend ({ 'currency': this.marketId (market), }, params)); }, async createOrder (market, type, side, amount, price = undefined, params = {}) { let order = { 'side': side, 'type': type, 'currency': this.marketId (market), 'amount': amount, }; if (type == 'limit') order['limit_price'] = price; let result = await this.privatePostOrdersNew (this.extend (order, params)); return { 'info': result, 'id': result, }; }, async cancelOrder (id) { return this.privatePostOrdersCancel ({ 'id': id }); }, async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { if (this.id == 'cryptocapital') throw new ExchangeError (this.id + ' is an abstract base API for _1btcxe'); let url = this.urls['api'] + '/' + path; if (api == 'public') { if (Object.keys (params).length) url += '?' + this.urlencode (params); } else { let query = this.extend ({ 'api_key': this.apiKey, 'nonce': this.nonce (), }, params); let request = this.json (query); query['signature'] = this.hmac (this.encode (request), this.encode (this.secret)); body = this.json (query); headers = { 'Content-Type': 'application/json' }; } let response = await this.fetch (url, method, headers, body); if ('errors' in response) { let errors = []; for (let e = 0; e < response['errors'].length; e++) { let error = response['errors'][e]; errors.push (error['code'] + ': ' + error['message']); } errors = errors.join (' '); throw new ExchangeError (this.id + ' ' + errors); } return this.fetch (url, method, headers, body); }, } //----------------------------------------------------------------------------- var _1btcxe = extend (cryptocapital, { 'id': '_1btcxe', 'name': '1BTCXE', 'countries': 'PA', // Panama 'comment': 'Crypto Capital API', 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27766049-2b294408-5ecc-11e7-85cc-adaff013dc1a.jpg', 'api': 'https://1btcxe.com/api', 'www': 'https://1btcxe.com', 'doc': 'https://1btcxe.com/api-docs.php', }, 'markets': { 'BTC/USD': { 'id': 'USD', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD' }, 'BTC/EUR': { 'id': 'EUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR' }, 'BTC/CNY': { 'id': 'CNY', 'symbol': 'BTC/CNY', 'base': 'BTC', 'quote': 'CNY' }, 'BTC/RUB': { 'id': 'RUB', 'symbol': 'BTC/RUB', 'base': 'BTC', 'quote': 'RUB' }, 'BTC/CHF': { 'id': 'CHF', 'symbol': 'BTC/CHF', 'base': 'BTC', 'quote': 'CHF' }, 'BTC/JPY': { 'id': 'JPY', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY' }, 'BTC/GBP': { 'id': 'GBP', 'symbol': 'BTC/GBP', 'base': 'BTC', 'quote': 'GBP' }, 'BTC/CAD': { 'id': 'CAD', 'symbol': 'BTC/CAD', 'base': 'BTC', 'quote': 'CAD' }, 'BTC/AUD': { 'id': 'AUD', 'symbol': 'BTC/AUD', 'base': 'BTC', 'quote': 'AUD' }, 'BTC/AED': { 'id': 'AED', 'symbol': 'BTC/AED', 'base': 'BTC', 'quote': 'AED' }, 'BTC/BGN': { 'id': 'BGN', 'symbol': 'BTC/BGN', 'base': 'BTC', 'quote': 'BGN' }, 'BTC/CZK': { 'id': 'CZK', 'symbol': 'BTC/CZK', 'base': 'BTC', 'quote': 'CZK' }, 'BTC/DKK': { 'id': 'DKK', 'symbol': 'BTC/DKK', 'base': 'BTC', 'quote': 'DKK' }, 'BTC/HKD': { 'id': 'HKD', 'symbol': 'BTC/HKD', 'base': 'BTC', 'quote': 'HKD' }, 'BTC/HRK': { 'id': 'HRK', 'symbol': 'BTC/HRK', 'base': 'BTC', 'quote': 'HRK' }, 'BTC/HUF': { 'id': 'HUF', 'symbol': 'BTC/HUF', 'base': 'BTC', 'quote': 'HUF' }, 'BTC/ILS': { 'id': 'ILS', 'symbol': 'BTC/ILS', 'base': 'BTC', 'quote': 'ILS' }, 'BTC/INR': { 'id': 'INR', 'symbol': 'BTC/INR', 'base': 'BTC', 'quote': 'INR' }, 'BTC/MUR': { 'id': 'MUR', 'symbol': 'BTC/MUR', 'base': 'BTC', 'quote': 'MUR' }, 'BTC/MXN': { 'id': 'MXN', 'symbol': 'BTC/MXN', 'base': 'BTC', 'quote': 'MXN' }, 'BTC/NOK': { 'id': 'NOK', 'symbol': 'BTC/NOK', 'base': 'BTC', 'quote': 'NOK' }, 'BTC/NZD': { 'id': 'NZD', 'symbol': 'BTC/NZD', 'base': 'BTC', 'quote': 'NZD' }, 'BTC/PLN': { 'id': 'PLN', 'symbol': 'BTC/PLN', 'base': 'BTC', 'quote': 'PLN' }, 'BTC/RON': { 'id': 'RON', 'symbol': 'BTC/RON', 'base': 'BTC', 'quote': 'RON' }, 'BTC/SEK': { 'id': 'SEK', 'symbol': 'BTC/SEK', 'base': 'BTC', 'quote': 'SEK' }, 'BTC/SGD': { 'id': 'SGD', 'symbol': 'BTC/SGD', 'base': 'BTC', 'quote': 'SGD' }, 'BTC/THB': { 'id': 'THB', 'symbol': 'BTC/THB', 'base': 'BTC', 'quote': 'THB' }, 'BTC/TRY': { 'id': 'TRY', 'symbol': 'BTC/TRY', 'base': 'BTC', 'quote': 'TRY' }, 'BTC/ZAR': { 'id': 'ZAR', 'symbol': 'BTC/ZAR', 'base': 'BTC', 'quote': 'ZAR' }, }, }) //----------------------------------------------------------------------------- var anxpro = { 'id': 'anxpro', 'name': 'ANXPro', 'countries': [ 'JP', 'SG', 'HK', 'NZ' ], 'version': '2', 'rateLimit': 1500, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27765983-fd8595da-5ec9-11e7-82e3-adb3ab8c2612.jpg', 'api': 'https://anxpro.com/api', 'www': 'https://anxpro.com', 'doc': [ 'http://docs.anxv2.apiary.io', 'https://anxpro.com/pages/api', ], }, 'api': { 'public': { 'get': [ '{currency_pair}/money/ticker', '{currency_pair}/money/depth/full', '{currency_pair}/money/trade/fetch', // disabled by ANXPro ], }, 'private': { 'post': [ '{currency_pair}/money/order/add', '{currency_pair}/money/order/cancel', '{currency_pair}/money/order/quote', '{currency_pair}/money/order/result', '{currency_pair}/money/orders', 'money/{currency}/address', 'money/{currency}/send_simple', 'money/info', 'money/trade/list', 'money/wallet/history', ], }, }, 'markets': { 'BTC/USD': { 'id': 'BTCUSD', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD' }, 'BTC/HKD': { 'id': 'BTCHKD', 'symbol': 'BTC/HKD', 'base': 'BTC', 'quote': 'HKD' }, 'BTC/EUR': { 'id': 'BTCEUR', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR' }, 'BTC/CAD': { 'id': 'BTCCAD', 'symbol': 'BTC/CAD', 'base': 'BTC', 'quote': 'CAD' }, 'BTC/AUD': { 'id': 'BTCAUD', 'symbol': 'BTC/AUD', 'base': 'BTC', 'quote': 'AUD' }, 'BTC/SGD': { 'id': 'BTCSGD', 'symbol': 'BTC/SGD', 'base': 'BTC', 'quote': 'SGD' }, 'BTC/JPY': { 'id': 'BTCJPY', 'symbol': 'BTC/JPY', 'base': 'BTC', 'quote': 'JPY' }, 'BTC/GBP': { 'id': 'BTCGBP', 'symbol': 'BTC/GBP', 'base': 'BTC', 'quote': 'GBP' }, 'BTC/NZD': { 'id': 'BTCNZD', 'symbol': 'BTC/NZD', 'base': 'BTC', 'quote': 'NZD' }, 'LTC/BTC': { 'id': 'LTCBTC', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC' }, 'DOGE/BTC': { 'id': 'DOGEBTC', 'symbol': 'DOGE/BTC', 'base': 'DOGE', 'quote': 'BTC' }, 'STR/BTC': { 'id': 'STRBTC', 'symbol': 'STR/BTC', 'base': 'STR', 'quote': 'BTC' }, 'XRP/BTC': { 'id': 'XRPBTC', 'symbol': 'XRP/BTC', 'base': 'XRP', 'quote': 'BTC' }, }, async fetchBalance (params = {}) { let response = await this.privatePostMoneyInfo (); let balance = response['data']; let currencies = Object.keys (balance['Wallets']); let result = { 'info': balance }; for (let c = 0; c < currencies.length; c++) { let currency = currencies[c]; let account = this.account (); if (currency in balance['Wallets']) { let wallet = balance['Wallets'][currency]; account['free'] = parseFloat (wallet['Available_Balance']['value']); account['total'] = parseFloat (wallet['Balance']['value']); account['used'] = account['total'] - account['free']; } result[currency] = account; } return result; }, async fetchOrderBook (market, params = {}) { let response = await this.publicGetCurrencyPairMoneyDepthFull (this.extend ({ 'currency_pair': this.marketId (market), }, params)); let orderbook = response['data']; let t = parseInt (orderbook['dataUpdateTime']); let timestamp = parseInt (t / 1000); let result = { 'bids': [], 'asks': [], 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), }; let sides = [ 'bids', 'asks' ]; for (let s = 0; s < sides.length; s++) { let side = sides[s]; let orders = orderbook[side]; for (let i = 0; i < orders.length; i++) { let order = orders[i]; let price = parseFloat (order['price']); let amount = parseFloat (order['amount']); result[side].push ([ price, amount ]); } } return result; }, async fetchTicker (market) { let response = await this.publicGetCurrencyPairMoneyTicker ({ 'currency_pair': this.marketId (market), }); let ticker = response['data']; let t = parseInt (ticker['dataUpdateTime']); let timestamp = parseInt (t / 1000); let bid = undefined; let ask = undefined; if (ticker['buy']['value']) bid = parseFloat (ticker['buy']['value']); if (ticker['sell']['value']) ask = parseFloat (ticker['sell']['value']); return { 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': parseFloat (ticker['high']['value']), 'low': parseFloat (ticker['low']['value']), 'bid': bid, 'ask': ask, 'vwap': parseFloat (ticker['vwap']['value']), 'open': undefined, 'close': undefined, 'first': undefined, 'last': parseFloat (ticker['last']['value']), 'change': undefined, 'percentage': undefined, 'average': parseFloat (ticker['avg']['value']), 'baseVolume': undefined, 'quoteVolume': parseFloat (ticker['vol']['value']), }; }, async fetchTrades (market, params = {}) { let error = this.id + ' switched off the trades endpoint, see their docs at http://docs.anxv2.apiary.io/ref