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
JavaScript
"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