This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
gekko/web/state/gekkoManager.js
T
2021-06-06 20:25:14 +02:00

254 lines
5.6 KiB
JavaScript

const _ = require('lodash');
const moment = require('moment');
const broadcast = require('./cache').get('broadcast');
const Logger = require('./logger');
const pipelineRunner = require('../../core/workers/pipeline/parent');
const reduceState = require('./reduceState.js');
const now = () => moment().format('YYYY-MM-DD HH:mm');
const GekkoManager = function() {
this.gekkos = {};
this.instances = {};
this.loggers = {};
this.archivedGekkos = {};
}
GekkoManager.prototype.add = function({mode, config}) {
// set type
let type;
if(mode === 'realtime') {
if(config.market && config.market.type)
type = config.market.type;
else
type = 'watcher';
} else {
type = '';
}
let logType = type;
if(logType === 'leech') {
if(config.trader && config.trader.enabled)
logType = 'tradebot';
else
logType = 'papertrader';
}
const date = now().replace(' ', '-').replace(':', '-');
const n = (Math.random() + '').slice(3);
const id = `${date}-${logType}-${n}`;
// make sure we catch events happening inside te gekko instance
config.childToParent.enabled = true;
const state = {
mode,
config,
id,
type,
logType,
active: true,
stopped: false,
errored: false,
errorMessage: false,
events: {
initial: {},
latest: {}
},
start: moment()
}
this.gekkos[id] = state;
this.loggers[id] = new Logger(id);
// start the actual instance
this.instances[id] = pipelineRunner(mode, config, this.handleRawEvent(id));
// after passing API credentials to the actual instance we mask them
if(logType === 'trader') {
config.trader.key = '[REDACTED]';
config.trader.secret = '[REDACTED]';
}
console.log(`${now()} Gekko ${id} started.`);
broadcast({
type: 'gekko_new',
id,
state
});
return state;
}
GekkoManager.prototype.handleRawEvent = function(id) {
const logger = this.loggers[id];
return (err, event) => {
if(err) {
return this.handleFatalError(id, err);
}
if(!event) {
return;
}
if(event.log) {
return logger.write(event.message);
}
if(event.type) {
this.handleGekkoEvent(id, event);
}
}
}
GekkoManager.prototype.handleGekkoEvent = function(id, event) {
this.gekkos[id] = reduceState(this.gekkos[id], event);
broadcast({
type: 'gekko_event',
id,
event
});
}
GekkoManager.prototype.handleFatalError = function(id, err) {
const state = this.gekkos[id];
if(!state || state.errored || state.stopped)
return;
state.errored = true;
state.errorMessage = err;
console.error('RECEIVED ERROR IN GEKKO INSTANCE', id);
console.error(err);
broadcast({
type: 'gekko_error',
id,
error: err
});
this.archive(id);
if(state.logType === 'watcher') {
this.handleWatcherError(state, id);
}
}
// There might be leechers depending on this watcher, if so
// figure out it we can safely start a new watcher without
// the leechers noticing.
GekkoManager.prototype.handleWatcherError = function(state, id) {
console.log(`${now()} A gekko watcher crashed.`);
if(!state.events.latest.candle) {
console.log(`${now()} was unable to start.`);
}
let latestCandleTime = moment.unix(0);
if(state.events.latest && state.events.latest.candle) {
latestCandleTime = state.events.latest.candle.start;
}
const leechers = _.values(this.gekkos)
.filter(gekko => {
if(gekko.type !== 'leech') {
return false;
}
if(_.isEqual(gekko.config.watch, state.config.watch)) {
return true;
}
});
if(leechers.length) {
console.log(`${now()} ${leechers.length} leecher(s) were depending on this watcher.`);
if(moment().diff(latestCandleTime, 'm') < 60) {
console.log(`${now()} Watcher had recent data, starting a new one in a minute.`);
// after a minute try to start a new one again..
setTimeout(() => {
const mode = 'realtime';
const config = state.config;
this.add({mode, config});
}, 1000 * 60);
} else {
console.log(`${now()} Watcher did not have recent data, killing its leechers.`);
leechers.forEach(leecher => this.stop(leecher.id));
}
}
}
GekkoManager.prototype.stop = function(id) {
if(!this.gekkos[id])
return false;
console.log(`${now()} stopping Gekko ${id}`);
this.gekkos[id].stopped = true;
this.gekkos[id].active = false;
// todo: graceful shutdown (via gekkoStream's
// finish function).
this.instances[id].kill();
broadcast({
type: 'gekko_stopped',
id
});
this.archive(id);
return true;
}
GekkoManager.prototype.archive = function(id) {
this.archivedGekkos[id] = this.gekkos[id];
this.archivedGekkos[id].stopped = true;
this.archivedGekkos[id].active = false;
delete this.gekkos[id];
broadcast({
type: 'gekko_archived',
id
});
}
GekkoManager.prototype.delete = function(id) {
if(this.gekkos[id]) {
throw new Error('Cannot delete a running Gekko, stop it first.');
}
if(!this.archivedGekkos[id]) {
throw new Error('Cannot delete unknown Gekko.');
}
console.log(`${now()} deleting Gekko ${id}`);
broadcast({
type: 'gekko_deleted',
id
});
delete this.archivedGekkos[id];
return true;
}
GekkoManager.prototype.archive = function(id) {
this.archivedGekkos[id] = this.gekkos[id];
this.archivedGekkos[id].stopped = true;
this.archivedGekkos[id].active = false;
delete this.gekkos[id];
broadcast({
type: 'gekko_archived',
id
});
}
GekkoManager.prototype.list = function() {
return { live: this.gekkos, archive: this.archivedGekkos };
}
module.exports = GekkoManager;