bot18
Version:
A high-frequency cryptocurrency trading bot by Zenbot creator @carlos8f
311 lines (260 loc) • 7.57 kB
JavaScript
const request = require('request');
const { Readable } = require('stream');
const DEFAULT_TIMEOUT = 10 * 1000; // 10 sec
class PublicClient {
constructor(apiURI = 'https://api.gdax.com', options = {}) {
this.productID = 'BTC-USD';
if (apiURI && !apiURI.startsWith('http')) {
process.emitWarning(
'`new PublicClient()` no longer accepts a product ID as the first argument. ',
'DeprecationWarning'
);
this.productID = apiURI;
apiURI = arguments[1] || 'https://api.gdax.com';
}
this.apiURI = apiURI;
this.API_LIMIT = 100;
this.timeout = +options.timeout > 0 ? options.timeout : DEFAULT_TIMEOUT;
}
get(...args) {
return this.request('get', ...args);
}
put(...args) {
return this.request('put', ...args);
}
post(...args) {
return this.request('post', ...args);
}
delete(...args) {
return this.request('delete', ...args);
}
addHeaders(obj, additional) {
obj.headers = obj.headers || {};
return Object.assign(
obj.headers,
{
'User-Agent': 'gdax-node-client',
Accept: 'application/json',
'Content-Type': 'application/json',
},
additional
);
}
makeRelativeURI(parts) {
return '/' + parts.join('/');
}
makeAbsoluteURI(relativeURI) {
return this.apiURI + relativeURI;
}
makeRequestCallback(callback, resolve, reject) {
return (err, response, data) => {
try {
data = JSON.parse(data);
} catch (e) {
data = null;
}
if (err) {
err.response = response;
err.data = data;
} else if (response.statusCode > 299) {
err = new Error(
`HTTP ${response.statusCode} Error: ${data && data.message}`
);
err.response = response;
err.data = data;
} else if (data === null) {
err = new Error('Response could not be parsed as JSON');
err.response = response;
err.data = data;
}
if (typeof callback === 'function') {
if (err) {
callback(err);
} else {
callback(null, response, data);
}
return;
}
if (err) {
reject(err);
} else {
resolve(data);
}
};
}
request(method, uriParts, opts = {}, callback) {
if (!callback && typeof opts === 'function') {
callback = opts;
opts = {};
}
Object.assign(opts, {
method: method.toUpperCase(),
uri: this.makeAbsoluteURI(this.makeRelativeURI(uriParts)),
qsStringifyOptions: { arrayFormat: 'repeat' },
timeout: this.timeout,
});
this.addHeaders(opts);
const p = new Promise((resolve, reject) => {
request(opts, this.makeRequestCallback(callback, resolve, reject));
});
if (callback) {
p.catch(() => {});
return undefined;
} else {
return p;
}
}
getProducts(callback) {
return this.get(['products'], callback);
}
getProductOrderBook(productID, args, callback) {
[productID, args, callback] = this._normalizeProductArgs(
productID,
args,
callback,
'getProductOrderBook'
);
const path = ['products', productID, 'book'];
return this.get(path, { qs: args }, callback);
}
getProductTicker(productID, callback) {
[productID, , callback] = this._normalizeProductArgs(
productID,
null,
callback,
'getProductTicker'
);
const path = ['products', productID, 'ticker'];
return this.get(path, callback);
}
getProductTrades(productID, args, callback) {
[productID, args, callback] = this._normalizeProductArgs(
productID,
args,
callback,
'getProductTrades'
);
const path = ['products', productID, 'trades'];
return this.get(path, { qs: args }, callback);
}
getProductTradeStream(productID, tradesFrom, tradesTo) {
if (!productID || typeof productID !== 'string') {
[tradesFrom, tradesTo] = Array.prototype.slice.call(arguments);
}
[productID] = this._normalizeProductArgs(
productID,
null,
null,
'getProductTradeStream'
);
let shouldStop = null;
if (typeof tradesTo === 'function') {
shouldStop = tradesTo;
tradesTo = null;
}
const rs = new Readable({ objectMode: true });
let started = false;
rs._read = () => {
if (!started) {
started = true;
fetchTrades.call(this, rs, tradesFrom, tradesTo, shouldStop, 0);
}
};
return rs;
function fetchTrades(stream, tradesFrom, tradesTo, shouldStop) {
let after = tradesFrom + this.API_LIMIT + 1;
let loop = true;
if (tradesTo && tradesTo <= after) {
after = tradesTo;
loop = false;
}
let opts = { before: tradesFrom, after: after, limit: this.API_LIMIT };
this.getProductTrades(productID, opts, (err, resp, data) => {
if (err) {
stream.emit('error', err);
return;
}
if (resp.statusCode === 429) {
// rate-limited, try again
setTimeout(() => {
fetchTrades.call(this, stream, tradesFrom, tradesTo, shouldStop);
}, 900);
return;
}
if (resp.statusCode !== 200) {
stream.emit(
'error',
new Error('Encountered status code ' + resp.statusCode)
);
}
for (let i = data.length - 1; i >= 0; i--) {
if (shouldStop && shouldStop(data[i])) {
stream.push(null);
return;
}
stream.push(data[i]);
}
if (!loop || data.length === 0) {
stream.push(null);
return;
}
fetchTrades.call(
this,
stream,
tradesFrom + this.API_LIMIT,
tradesTo,
shouldStop
);
});
}
}
getProductHistoricRates(productID, args, callback) {
[productID, args, callback] = this._normalizeProductArgs(
productID,
args,
callback,
'getProductHistoricRates'
);
const path = ['products', productID, 'candles'];
return this.get(path, { qs: args }, callback);
}
getProduct24HrStats(productID, callback) {
[productID, , callback] = this._normalizeProductArgs(
productID,
null,
callback,
'getProduct24HrStats'
);
const path = ['products', productID, 'stats'];
return this.get(path, callback);
}
getCurrencies(callback) {
return this.get(['currencies'], callback);
}
getTime(callback) {
return this.get(['time'], callback);
}
_normalizeProductArgs(productID, args, callback, caller) {
this._deprecationWarningIfProductIdMissing(productID, caller);
callback = [callback, args, productID].find(byType('function'));
args = [args, productID, {}].find(byType('object'));
productID = [productID, this.productID].find(byType('string'));
if (!productID) {
throw new Error('No productID specified.');
}
return [productID, args, callback];
}
_deprecationWarningIfProductIdMissing(productID, caller) {
if (!productID || typeof productID !== 'string') {
process.emitWarning(
`\`${caller}()\` now requires a product ID as the first argument. ` +
`Attempting to use PublicClient#productID (${
this.productID
}) instead.`,
'DeprecationWarning'
);
}
}
}
const byType = type => o => o !== null && typeof o === type;
module.exports = exports = PublicClient;