first commit
This commit is contained in:
commit
aa7869eb33
386 files changed
+69375
No files matched your search
@@ -0,0 +1,150 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const util = require('../../core/util');
|
||||
const config = util.getConfig();
|
||||
const dirs = util.dirs();
|
||||
const log = require(dirs.core + 'log');
|
||||
|
||||
const talib = require(dirs.core + 'talib');
|
||||
const tulind = require(dirs.core + 'tulind');
|
||||
|
||||
const allowedTalibIndicators = _.keys(talib);
|
||||
const allowedTulipIndicators = _.keys(tulind);
|
||||
|
||||
const AsyncIndicatorRunner = function() {
|
||||
this.talibIndicators = {};
|
||||
this.tulipIndicators = {};
|
||||
|
||||
this.candleProps = {
|
||||
open: [],
|
||||
high: [],
|
||||
low: [],
|
||||
close: [],
|
||||
volume: []
|
||||
};
|
||||
|
||||
this.candlePropsCacheSize = 1000;
|
||||
|
||||
this.inflight = false;
|
||||
this.backlog = [];
|
||||
this.age = 0;
|
||||
}
|
||||
|
||||
AsyncIndicatorRunner.prototype.processCandle = function(candle, next) {
|
||||
if(this.inflight) {
|
||||
return this.backlog.push({candle, next});
|
||||
}
|
||||
|
||||
this.age++;
|
||||
this.inflight = true;
|
||||
|
||||
this.candleProps.open.push(candle.open);
|
||||
this.candleProps.high.push(candle.high);
|
||||
this.candleProps.low.push(candle.low);
|
||||
this.candleProps.close.push(candle.close);
|
||||
this.candleProps.volume.push(candle.volume);
|
||||
|
||||
if(this.age > this.candlePropsCacheSize) {
|
||||
this.candleProps.open.shift();
|
||||
this.candleProps.high.shift();
|
||||
this.candleProps.low.shift();
|
||||
this.candleProps.close.shift();
|
||||
this.candleProps.volume.shift();
|
||||
}
|
||||
|
||||
this.calculateIndicators(next);
|
||||
}
|
||||
|
||||
AsyncIndicatorRunner.prototype.calculateIndicators = function(next) {
|
||||
const done = _.after(
|
||||
_.size(this.talibIndicators) + _.size(this.tulipIndicators),
|
||||
this.handlePostFlight(next)
|
||||
);
|
||||
|
||||
// handle result from talib
|
||||
const talibResultHander = name => (err, result) => {
|
||||
if(err)
|
||||
util.die('TALIB ERROR:', err);
|
||||
|
||||
this.talibIndicators[name].result = _.mapValues(result, v => _.last(v));
|
||||
done();
|
||||
}
|
||||
|
||||
// handle result from talib
|
||||
_.each(
|
||||
this.talibIndicators,
|
||||
(indicator, name) => indicator.run(
|
||||
this.candleProps,
|
||||
talibResultHander(name)
|
||||
)
|
||||
);
|
||||
|
||||
// handle result from tulip
|
||||
var tulindResultHander = name => (err, result) => {
|
||||
if(err)
|
||||
util.die('TULIP ERROR:', err);
|
||||
|
||||
this.tulipIndicators[name].result = _.mapValues(result, v => _.last(v));
|
||||
done();
|
||||
}
|
||||
|
||||
// handle result from tulip indicators
|
||||
_.each(
|
||||
this.tulipIndicators,
|
||||
(indicator, name) => indicator.run(
|
||||
this.candleProps,
|
||||
tulindResultHander(name)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
AsyncIndicatorRunner.prototype.handlePostFlight = function(next) {
|
||||
return () => {
|
||||
next();
|
||||
this.inflight = false;
|
||||
|
||||
if(this.backlog.length) {
|
||||
const { candle, next } = this.backlog.shift();
|
||||
this.processCandle(candle, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncIndicatorRunner.prototype.addTalibIndicator = function(name, type, parameters) {
|
||||
if(!talib)
|
||||
util.die('Talib is not enabled');
|
||||
|
||||
if(!_.contains(allowedTalibIndicators, type))
|
||||
util.die('I do not know the talib indicator ' + type);
|
||||
|
||||
if(this.setup)
|
||||
util.die('Can only add talib indicators in the init method!');
|
||||
|
||||
var basectx = this;
|
||||
|
||||
this.talibIndicators[name] = {
|
||||
run: talib[type].create(parameters),
|
||||
result: NaN
|
||||
}
|
||||
}
|
||||
|
||||
AsyncIndicatorRunner.prototype.addTulipIndicator = function(name, type, parameters) {
|
||||
if(!tulind) {
|
||||
util.die('Tulip indicators is not enabled');
|
||||
}
|
||||
|
||||
if(!_.contains(allowedTulipIndicators, type))
|
||||
util.die('I do not know the tulip indicator ' + type);
|
||||
|
||||
if(this.setup)
|
||||
util.die('Can only add tulip indicators in the init method!');
|
||||
|
||||
var basectx = this;
|
||||
|
||||
this.tulipIndicators[name] = {
|
||||
run: tulind[type].create(parameters),
|
||||
result: NaN
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AsyncIndicatorRunner;
|
||||
@@ -0,0 +1,334 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const util = require('../../core/util');
|
||||
const config = util.getConfig();
|
||||
const dirs = util.dirs();
|
||||
const log = require(dirs.core + 'log');
|
||||
|
||||
const ENV = util.gekkoEnv();
|
||||
const mode = util.gekkoMode();
|
||||
const startTime = util.getStartTime();
|
||||
|
||||
const indicatorsPath = dirs.methods + 'indicators/';
|
||||
const indicatorFiles = fs.readdirSync(indicatorsPath);
|
||||
const Indicators = {};
|
||||
|
||||
const AsyncIndicatorRunner = require('./asyncIndicatorRunner');
|
||||
|
||||
_.each(indicatorFiles, function(indicator) {
|
||||
const indicatorName = indicator.split(".")[0];
|
||||
if (indicatorName[0] != "_")
|
||||
try {
|
||||
Indicators[indicatorName] = require(indicatorsPath + indicator);
|
||||
} catch (e) {
|
||||
log.error("Failed to load indicator", indicatorName);
|
||||
}
|
||||
});
|
||||
|
||||
const allowedIndicators = _.keys(Indicators);
|
||||
|
||||
var Base = function(settings) {
|
||||
_.bindAll(this);
|
||||
|
||||
// properties
|
||||
this.age = 0;
|
||||
this.processedTicks = 0;
|
||||
this.setup = false;
|
||||
this.settings = settings;
|
||||
this.tradingAdvisor = config.tradingAdvisor;
|
||||
// defaults
|
||||
this.priceValue = 'close';
|
||||
this.indicators = {};
|
||||
this.asyncTick = false;
|
||||
this.deferredTicks = [];
|
||||
|
||||
this.propogatedAdvices = 0;
|
||||
|
||||
this.completedWarmup = false;
|
||||
|
||||
this.asyncIndicatorRunner = new AsyncIndicatorRunner();
|
||||
|
||||
this._currentDirection;
|
||||
|
||||
// make sure we have all methods
|
||||
_.each(['init', 'check'], function(fn) {
|
||||
if(!this[fn])
|
||||
util.die('No ' + fn + ' function in this strategy found.')
|
||||
}, this);
|
||||
|
||||
if(!this.update)
|
||||
this.update = function() {};
|
||||
|
||||
if(!this.end)
|
||||
this.end = function() {};
|
||||
|
||||
if(!this.onTrade)
|
||||
this.onTrade = function() {};
|
||||
|
||||
// let's run the implemented starting point
|
||||
this.init();
|
||||
|
||||
if(_.isNumber(this.requiredHistory)) {
|
||||
log.debug('Ignoring strategy\'s required history, using the "config.tradingAdvisor.historySize" instead.');
|
||||
}
|
||||
this.requiredHistory = config.tradingAdvisor.historySize;
|
||||
|
||||
if(!config.debug || !this.log)
|
||||
this.log = function() {};
|
||||
|
||||
this.setup = true;
|
||||
|
||||
if(_.size(this.asyncIndicatorRunner.talibIndicators) || _.size(this.asyncIndicatorRunner.tulipIndicators))
|
||||
this.asyncTick = true;
|
||||
else
|
||||
delete this.asyncIndicatorRunner;
|
||||
}
|
||||
|
||||
// teach our base trading method events
|
||||
util.makeEventEmitter(Base);
|
||||
|
||||
Base.prototype.tick = function(candle, done) {
|
||||
this.age++;
|
||||
|
||||
const afterAsync = () => {
|
||||
this.calculateSyncIndicators(candle, done);
|
||||
}
|
||||
|
||||
if(this.asyncTick) {
|
||||
this.asyncIndicatorRunner.processCandle(candle, () => {
|
||||
|
||||
if(!this.talibIndicators) {
|
||||
this.talibIndicators = this.asyncIndicatorRunner.talibIndicators;
|
||||
this.tulipIndicators = this.asyncIndicatorRunner.tulipIndicators;
|
||||
}
|
||||
|
||||
afterAsync();
|
||||
});
|
||||
} else {
|
||||
afterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
Base.prototype.isBusy = function() {
|
||||
if(!this.asyncTick)
|
||||
return false;
|
||||
|
||||
return this.asyncIndicatorRunner.inflight;
|
||||
}
|
||||
|
||||
Base.prototype.calculateSyncIndicators = function(candle, done) {
|
||||
// update all indicators
|
||||
var price = candle[this.priceValue];
|
||||
_.each(this.indicators, function(i) {
|
||||
if(i.input === 'price')
|
||||
i.update(price);
|
||||
if(i.input === 'candle')
|
||||
i.update(candle);
|
||||
},this);
|
||||
|
||||
this.propogateTick(candle);
|
||||
|
||||
return done();
|
||||
}
|
||||
|
||||
Base.prototype.propogateTick = function(candle) {
|
||||
this.candle = candle;
|
||||
this.update(candle);
|
||||
|
||||
this.processedTicks++;
|
||||
var isAllowedToCheck = this.requiredHistory <= this.age;
|
||||
|
||||
if(!this.completedWarmup) {
|
||||
|
||||
// in live mode we might receive more candles
|
||||
// than minimally needed. In that case check
|
||||
// whether candle start time is > startTime
|
||||
var isPremature = false;
|
||||
|
||||
if(mode === 'realtime') {
|
||||
const startTimeMinusCandleSize = startTime
|
||||
.clone()
|
||||
.subtract(this.tradingAdvisor.candleSize, "minutes");
|
||||
|
||||
isPremature = candle.start < startTimeMinusCandleSize;
|
||||
}
|
||||
|
||||
if(isAllowedToCheck && !isPremature) {
|
||||
this.completedWarmup = true;
|
||||
this.emit(
|
||||
'stratWarmupCompleted',
|
||||
{start: candle.start.clone()}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.completedWarmup) {
|
||||
this.log(candle);
|
||||
this.check(candle);
|
||||
|
||||
if(
|
||||
this.asyncTick &&
|
||||
this.hasSyncIndicators &&
|
||||
this.deferredTicks.length
|
||||
) {
|
||||
return this.tick(this.deferredTicks.shift())
|
||||
}
|
||||
}
|
||||
|
||||
const indicators = {};
|
||||
_.each(this.indicators, (indicator, name) => {
|
||||
indicators[name] = indicator.result;
|
||||
});
|
||||
|
||||
_.each(this.tulipIndicators, (indicator, name) => {
|
||||
indicators[name] = indicator.result.result
|
||||
? indicator.result.result
|
||||
: indicator.result;
|
||||
});
|
||||
|
||||
_.each(this.talibIndicators, (indicator, name) => {
|
||||
indicators[name] = indicator.result.outReal
|
||||
? indicator.result.outReal
|
||||
: indicator.result;
|
||||
});
|
||||
|
||||
this.emit('stratUpdate', {
|
||||
date: candle.start.clone(),
|
||||
indicators
|
||||
});
|
||||
|
||||
// are we totally finished?
|
||||
const completed = this.age === this.processedTicks;
|
||||
if(completed && this.finishCb)
|
||||
this.finishCb();
|
||||
}
|
||||
|
||||
Base.prototype.processTrade = function(trade) {
|
||||
if(
|
||||
this._pendingTriggerAdvice &&
|
||||
trade.action === 'sell' &&
|
||||
this._pendingTriggerAdvice === trade.adviceId
|
||||
) {
|
||||
// This trade came from a trigger of the previous advice,
|
||||
// update stored direction
|
||||
this._currentDirection = 'short';
|
||||
this._pendingTriggerAdvice = null;
|
||||
}
|
||||
|
||||
this.onTrade(trade);
|
||||
}
|
||||
|
||||
Base.prototype.addTalibIndicator = function(name, type, parameters) {
|
||||
this.asyncIndicatorRunner.addTalibIndicator(name, type, parameters);
|
||||
}
|
||||
|
||||
Base.prototype.addTulipIndicator = function(name, type, parameters) {
|
||||
this.asyncIndicatorRunner.addTulipIndicator(name, type, parameters);
|
||||
}
|
||||
|
||||
Base.prototype.addIndicator = function(name, type, parameters) {
|
||||
if(!_.contains(allowedIndicators, type))
|
||||
util.die('I do not know the indicator ' + type);
|
||||
|
||||
if(this.setup)
|
||||
util.die('Can only add indicators in the init method!');
|
||||
|
||||
return this.indicators[name] = new Indicators[type](parameters);
|
||||
|
||||
// some indicators need a price stream, others need full candles
|
||||
}
|
||||
|
||||
Base.prototype.advice = function(newDirection) {
|
||||
// ignore legacy soft advice
|
||||
if(!newDirection) {
|
||||
return;
|
||||
}
|
||||
|
||||
let trigger;
|
||||
if(_.isObject(newDirection)) {
|
||||
if(!_.isString(newDirection.direction)) {
|
||||
log.error('Strategy emitted unparsable advice:', newDirection);
|
||||
return;
|
||||
}
|
||||
|
||||
if(newDirection.direction === this._currentDirection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_.isObject(newDirection.trigger)) {
|
||||
if(newDirection.direction !== 'long') {
|
||||
log.warn(
|
||||
'Strategy adviced a stop on not long, this is not supported.',
|
||||
'As such the stop is ignored'
|
||||
);
|
||||
} else {
|
||||
|
||||
// the trigger is implemented in a trader
|
||||
trigger = newDirection.trigger;
|
||||
|
||||
if(trigger.trailPercentage && !trigger.trailValue) {
|
||||
trigger.trailValue = trigger.trailPercentage / 100 * this.candle.close;
|
||||
log.info('[StratRunner] Trailing stop trail value specified as percentage, setting to:', trigger.trailValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newDirection = newDirection.direction;
|
||||
}
|
||||
|
||||
if(newDirection === this._currentDirection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(newDirection === 'short' && this._pendingTriggerAdvice) {
|
||||
this._pendingTriggerAdvice = null;
|
||||
}
|
||||
|
||||
this._currentDirection = newDirection;
|
||||
|
||||
this.propogatedAdvices++;
|
||||
|
||||
const advice = {
|
||||
id: 'advice-' + this.propogatedAdvices,
|
||||
recommendation: newDirection
|
||||
};
|
||||
|
||||
if(trigger) {
|
||||
advice.trigger = trigger;
|
||||
this._pendingTriggerAdvice = 'advice-' + this.propogatedAdvices;
|
||||
} else {
|
||||
this._pendingTriggerAdvice = null;
|
||||
}
|
||||
|
||||
this.emit('advice', advice);
|
||||
|
||||
return this.propogatedAdvices;
|
||||
}
|
||||
|
||||
Base.prototype.notify = function(content) {
|
||||
this.emit('stratNotification', {
|
||||
content,
|
||||
date: new Date(),
|
||||
})
|
||||
}
|
||||
|
||||
Base.prototype.finish = function(done) {
|
||||
// Because the strategy might be async we need
|
||||
// to be sure we only stop after all candles are
|
||||
// processed.
|
||||
if(!this.asyncTick) {
|
||||
this.end();
|
||||
return done();
|
||||
}
|
||||
|
||||
if(this.age === this.processedTicks) {
|
||||
this.end();
|
||||
return done();
|
||||
}
|
||||
|
||||
// we are not done, register cb
|
||||
// and call after we are..
|
||||
this.finishCb = done;
|
||||
}
|
||||
|
||||
module.exports = Base;
|
||||
@@ -0,0 +1,122 @@
|
||||
var util = require('../../core/util');
|
||||
var _ = require('lodash');
|
||||
var fs = require('fs');
|
||||
var toml = require('toml');
|
||||
|
||||
var config = util.getConfig();
|
||||
var dirs = util.dirs();
|
||||
var log = require(dirs.core + 'log');
|
||||
var CandleBatcher = require(dirs.core + 'candleBatcher');
|
||||
|
||||
var moment = require('moment');
|
||||
var isLeecher = config.market && config.market.type === 'leech';
|
||||
|
||||
var Actor = function(done) {
|
||||
_.bindAll(this);
|
||||
|
||||
this.done = done;
|
||||
|
||||
this.batcher = new CandleBatcher(config.tradingAdvisor.candleSize);
|
||||
|
||||
this.strategyName = config.tradingAdvisor.method;
|
||||
|
||||
this.setupStrategy();
|
||||
|
||||
var mode = util.gekkoMode();
|
||||
|
||||
// the stitcher will try to pump in historical data
|
||||
// so that the strat can use this data as a "warmup period"
|
||||
//
|
||||
// the realtime "leech" market won't use the stitcher
|
||||
if(mode === 'realtime' && !isLeecher) {
|
||||
var Stitcher = require(dirs.tools + 'dataStitcher');
|
||||
var stitcher = new Stitcher(this.batcher);
|
||||
stitcher.prepareHistoricalData(done);
|
||||
} else
|
||||
done();
|
||||
}
|
||||
|
||||
Actor.prototype.setupStrategy = function() {
|
||||
|
||||
if(!fs.existsSync(dirs.methods + this.strategyName + '.js'))
|
||||
util.die('Gekko can\'t find the strategy "' + this.strategyName + '"');
|
||||
|
||||
log.info('\t', 'Using the strategy: ' + this.strategyName);
|
||||
|
||||
const strategy = require(dirs.methods + this.strategyName);
|
||||
|
||||
// bind all trading strategy specific functions
|
||||
// to the WrappedStrategy.
|
||||
const WrappedStrategy = require('./baseTradingMethod');
|
||||
|
||||
_.each(strategy, function(fn, name) {
|
||||
WrappedStrategy.prototype[name] = fn;
|
||||
});
|
||||
|
||||
let stratSettings;
|
||||
if(config[this.strategyName]) {
|
||||
stratSettings = config[this.strategyName];
|
||||
}
|
||||
|
||||
this.strategy = new WrappedStrategy(stratSettings);
|
||||
this.strategy
|
||||
.on(
|
||||
'stratWarmupCompleted',
|
||||
e => this.deferredEmit('stratWarmupCompleted', e)
|
||||
)
|
||||
.on('advice', this.relayAdvice)
|
||||
.on(
|
||||
'stratUpdate',
|
||||
e => this.deferredEmit('stratUpdate', e)
|
||||
).on('stratNotification',
|
||||
e => this.deferredEmit('stratNotification', e)
|
||||
)
|
||||
|
||||
this.strategy
|
||||
.on('tradeCompleted', this.processTradeCompleted);
|
||||
|
||||
this.batcher
|
||||
.on('candle', _candle => {
|
||||
const { id, ...candle } = _candle;
|
||||
this.deferredEmit('stratCandle', candle);
|
||||
this.emitStratCandle(candle);
|
||||
});
|
||||
}
|
||||
|
||||
// HANDLERS
|
||||
// process the 1m candles
|
||||
Actor.prototype.processCandle = function(candle, done) {
|
||||
this.candle = candle;
|
||||
const completedBatch = this.batcher.write([candle]);
|
||||
if(completedBatch) {
|
||||
this.next = done;
|
||||
} else {
|
||||
done();
|
||||
this.next = false;
|
||||
}
|
||||
this.batcher.flush();
|
||||
}
|
||||
|
||||
// propogate a custom sized candle to the trading strategy
|
||||
Actor.prototype.emitStratCandle = function(candle) {
|
||||
const next = this.next || _.noop;
|
||||
this.strategy.tick(candle, next);
|
||||
}
|
||||
|
||||
Actor.prototype.processTradeCompleted = function(trade) {
|
||||
this.strategy.processTrade(trade);
|
||||
}
|
||||
|
||||
// pass through shutdown handler
|
||||
Actor.prototype.finish = function(done) {
|
||||
this.strategy.finish(done);
|
||||
}
|
||||
|
||||
// EMITTERS
|
||||
Actor.prototype.relayAdvice = function(advice) {
|
||||
advice.date = this.candle.start.clone().add(1, 'minute');
|
||||
this.deferredEmit('advice', advice);
|
||||
}
|
||||
|
||||
|
||||
module.exports = Actor;
|
||||
Reference in new issue
Block a user