252 lines
6.2 KiB
JavaScript
252 lines
6.2 KiB
JavaScript
/*
|
|
|
|
A pipeline implements a full Gekko Flow based on a config and
|
|
a mode. The mode is an abstraction that tells Gekko what market
|
|
to load (realtime, backtesting or importing) while making sure
|
|
all enabled plugins are actually supported by that market.
|
|
|
|
Read more here:
|
|
https://gekko.wizb.it/docs/internals/architecture.html
|
|
|
|
*/
|
|
|
|
|
|
var util = require('./util');
|
|
var dirs = util.dirs();
|
|
|
|
var _ = require('lodash');
|
|
var async = require('async');
|
|
|
|
var log = require(dirs.core + 'log');
|
|
|
|
var pipeline = (settings) => {
|
|
|
|
var mode = settings.mode;
|
|
var config = settings.config;
|
|
|
|
// prepare a GekkoStream
|
|
var GekkoStream = require(dirs.core + 'gekkoStream');
|
|
|
|
// all plugins
|
|
var plugins = [];
|
|
// all emitting plugins
|
|
var emitters = {};
|
|
// all plugins interested in candles
|
|
var candleConsumers = [];
|
|
|
|
// utility to check and load plugins.
|
|
var pluginHelper = require(dirs.core + 'pluginUtil');
|
|
|
|
// meta information about every plugin that tells Gekko
|
|
// something about every available plugin
|
|
var pluginParameters = require(dirs.gekko + 'plugins');
|
|
// meta information about the events plugins can broadcast
|
|
// and how they should hooked up to consumers.
|
|
var subscriptions = require(dirs.gekko + 'subscriptions');
|
|
|
|
var market;
|
|
|
|
// Instantiate each enabled plugin
|
|
var loadPlugins = function(next) {
|
|
// load all plugins
|
|
async.mapSeries(
|
|
pluginParameters,
|
|
pluginHelper.load,
|
|
function(error, _plugins) {
|
|
if(error)
|
|
return util.die(error, true);
|
|
|
|
plugins = _.compact(_plugins);
|
|
next();
|
|
}
|
|
);
|
|
};
|
|
|
|
// Some plugins emit their own events, store
|
|
// a reference to those plugins.
|
|
var referenceEmitters = function(next) {
|
|
|
|
_.each(plugins, function(plugin) {
|
|
if(plugin.meta.emits)
|
|
emitters[plugin.meta.slug] = plugin;
|
|
});
|
|
|
|
next();
|
|
}
|
|
|
|
// Subscribe all plugins to other emitting plugins
|
|
var subscribePlugins = function(next) {
|
|
// events broadcasted by plugins
|
|
var pluginSubscriptions = _.filter(
|
|
subscriptions,
|
|
sub => sub.emitter !== 'market'
|
|
);
|
|
|
|
// some events can be broadcasted by different
|
|
// plugins, however the pipeline only allows a single
|
|
// emitting plugin for each event to be enabled.
|
|
_.each(
|
|
pluginSubscriptions.filter(s => _.isArray(s.emitter)),
|
|
subscription => {
|
|
// cache full list
|
|
subscription.emitters = subscription.emitter;
|
|
var singleEventEmitters = subscription.emitter
|
|
.filter(
|
|
s => _.size(plugins.filter(p => p.meta.slug === s))
|
|
);
|
|
|
|
if(_.size(singleEventEmitters) > 1) {
|
|
var error = `Multiple plugins are broadcasting`;
|
|
error += ` the event "${subscription.event}" (${singleEventEmitters.join(',')}).`;
|
|
error += 'This is unsupported.'
|
|
util.die(error);
|
|
} else {
|
|
subscription.emitter = _.first(singleEventEmitters);
|
|
}
|
|
}
|
|
);
|
|
|
|
// subscribe interested plugins to
|
|
// emitting plugins
|
|
_.each(plugins, function(plugin) {
|
|
_.each(pluginSubscriptions, function(sub) {
|
|
|
|
if(plugin[sub.handler]) {
|
|
// if a plugin wants to listen
|
|
// to something disabled
|
|
if(!emitters[sub.emitter]) {
|
|
if(!plugin.meta.greedy) {
|
|
|
|
let emitterMessage;
|
|
if(sub.emitters) {
|
|
emitterMessage = 'all of the emitting plugins [ ';
|
|
emitterMessage += sub.emitters.join(', ');
|
|
emitterMessage += ' ] are disabled.';
|
|
} else {
|
|
emitterMessage += 'the emitting plugin (' + sub.emitter;
|
|
emitterMessage += ')is disabled.'
|
|
}
|
|
|
|
log.error([
|
|
plugin.meta.name,
|
|
'wanted to listen to event',
|
|
sub.event + ',',
|
|
'however',
|
|
emitterMessage,
|
|
plugin.meta.name + ' might malfunction because of it.'
|
|
].join(' '));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// attach handler
|
|
emitters[sub.emitter]
|
|
.on(sub.event,
|
|
plugin[
|
|
sub.handler
|
|
])
|
|
}
|
|
|
|
});
|
|
});
|
|
|
|
// events broadcasted by the market
|
|
var marketSubscriptions = _.filter(
|
|
subscriptions,
|
|
{emitter: 'market'}
|
|
);
|
|
|
|
// subscribe plugins to the market
|
|
_.each(plugins, function(plugin) {
|
|
_.each(marketSubscriptions, function(sub) {
|
|
|
|
if(plugin[sub.handler]) {
|
|
if(sub.event === 'candle')
|
|
candleConsumers.push(plugin);
|
|
}
|
|
|
|
});
|
|
});
|
|
|
|
next();
|
|
}
|
|
|
|
var prepareMarket = function(next) {
|
|
if(mode === 'backtest' && config.backtest.daterange === 'scan')
|
|
require(dirs.core + 'prepareDateRange')(next);
|
|
else
|
|
next();
|
|
}
|
|
|
|
var setupMarket = function(next) {
|
|
// load a market based on the config (or fallback to mode)
|
|
let marketType;
|
|
if(config.market)
|
|
marketType = config.market.type;
|
|
else
|
|
marketType = mode;
|
|
|
|
var Market = require(dirs.markets + marketType);
|
|
|
|
market = new Market(config);
|
|
|
|
next();
|
|
}
|
|
|
|
var subscribePluginsToMarket = function(next) {
|
|
|
|
// events broadcasted by the market
|
|
var marketSubscriptions = _.filter(
|
|
subscriptions,
|
|
{emitter: 'market'}
|
|
);
|
|
|
|
_.each(plugins, function(plugin) {
|
|
_.each(marketSubscriptions, function(sub) {
|
|
|
|
if(sub.event === 'candle')
|
|
// these are handled via the market stream
|
|
return;
|
|
|
|
if(plugin[sub.handler]) {
|
|
market.on(sub.event, plugin[sub.handler]);
|
|
}
|
|
|
|
});
|
|
});
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
log.info('Setting up Gekko in', mode, 'mode');
|
|
log.info('');
|
|
|
|
async.series(
|
|
[
|
|
loadPlugins,
|
|
referenceEmitters,
|
|
subscribePlugins,
|
|
prepareMarket,
|
|
setupMarket,
|
|
subscribePluginsToMarket
|
|
],
|
|
function() {
|
|
|
|
var gekkoStream = new GekkoStream(plugins);
|
|
|
|
market
|
|
.pipe(gekkoStream)
|
|
|
|
// convert JS objects to JSON string
|
|
// .pipe(new require('stringify-stream')())
|
|
// output to standard out
|
|
// .pipe(process.stdout);
|
|
|
|
market.on('end', gekkoStream.finalize);
|
|
}
|
|
);
|
|
|
|
}
|
|
|
|
module.exports = pipeline; |