gekko-api-client
Version:
A client for the Gekko Trading Bot REST API.
198 lines (182 loc) • 5.34 kB
JavaScript
const _ = require('lodash');
const request = require('request');
const WebSocket = require('ws');
const DEFAULT_GEKKO_PORT = 3000;
const GekkoApiClient = function (host, port, options) {
this.host = host;
this.port = port;
this.options = options;
}
GekkoApiClient.prototype = {
/**
* Get a list of available candle ranges.
*
* @param {*} watch - the "watch" gekko config property
*/
scan: async function (watch) {
return this._post('/api/scan', { watch });
},
/**
* Start a historical data import.
*
* @param {*} from - momentjs object
* @param {*} to - momentjs object
* @param {*} watch - the "watch" gekko config property
* @param {*} otherConfig - optional root gekko configuration object
*/
import: async function (from, to, watch, otherConfig) {
let config = !otherConfig ? {} : _.clone(otherConfig);
config.watch = watch;
config.importer = {
daterange: {
from: from.utc().format(),
to: to.utc().format()
}
};
config.candleWriter = {
enabled: true
};
return this._post('/api/import', config);
},
/**
* Start an import job, and resolve when the job's done.
*
* @param {*} from - momentjs object
* @param {*} to - momentjs object
* @param {*} watch - the "watch" gekko config property
* @param {*} onUpdate - callback function for observing importer updates for this job
*/
importAndWait: async function (from, to, watch, onUpdate) {
const self = this;
return new Promise((resolve, reject) => {
try {
let ws = self.newWebsocket();
let started = false;
let done = false;
let importId;
let closeSocket = function () {
done = true;
ws.close();
};
ws.on('open', async function open() {
try {
started = true;
let result = await self.import(from, to, watch);
importId = result.id;
} catch (e) {
reject(e);
closeSocket();
}
});
ws.on('close', function close() {
if (!started) {
reject('failed opening websocket');
} else if (!done) {
reject('websocket closed early');
} else {
self._verbose('websocket closed');
}
});
ws.on('message', function incoming(data) {
data = JSON.parse(data);
if (!!data && !!importId && data.import_id === importId) {
if (data.type === 'import_update' && !!data.updates && !!data.updates.done) {
resolve();
closeSocket();
} else if (data.type === 'import_error') {
reject(data);
closeSocket();
}
if (!!onUpdate) {
onUpdate(data);
}
}
});
} catch (e) {
reject(e);
}
});
},
/**
* Get candle data for charting.
*
* @param {*} from - momentjs object
* @param {*} to - momentjs object
* @param {*} candleSize - size of candle, in minutes
* @param {*} watch - gekko config watch object, with the exchange name and currency pair
*/
getCandles: async function (from, to, candleSize, watch) {
return this._post('/api/getCandles', {
watch: watch,
daterange: {
from: from.utc().format(),
to: to.utc().format()
},
candleSize: candleSize
});
},
/**
* Run a backtest and resolve the results when it's done.
*
* @param {*} config
*/
runBacktest: async function (config) {
return this._post('/api/backtest', config);
},
/**
* Send an HTTP POST request with a json request body.
*
* @param {*} path - api endpoint path
* @param {*} json - json request body object
*/
_post: async function (path, json) {
const options = {
uri: 'http://' + this.host + ':' + (this.port || DEFAULT_GEKKO_PORT) + path,
method: 'POST',
json: json
};
this._verbose(options);
const self = this;
return new Promise((resolve, reject) => {
const startMs = Date.now();
request.post(options, function (error, response, body) {
const endMs = Date.now();
const durationS = (endMs - startMs) / 1000;
try {
if (!!response && !!response.statusCode && response.statusCode >= 200 && response.statusCode <= 299) {
resolve(body);
self._verbose(body);
} else {
reject({error, options, response, body, startMs, endMs, durationS});
}
} catch (e) {
reject({error, options, response, body, startMs, endMs, durationS, e});
}
});
});
},
/**
* Open a new gekko websocket.
*/
newWebsocket: function () {
return new WebSocket('ws://' + this.host + ':' + (this.port || DEFAULT_GEKKO_PORT) + '/', {
perMessageDeflate: false
});
},
/**
* Logging.
*
* @param {...any} args
*/
_verbose: function (...args) {
if (!!this.options && !!this.options.verbose) {
let verbose = this.options.verbose;
if (_.isFunction(verbose)) {
verbose(...args);
} else {
console.log(_.map(args, JSON.stringify));
}
}
}
};
module.exports = GekkoApiClient;