Skip to content

Instantly share code, notes, and snippets.

@Gab0
Last active November 24, 2017 07:07
Show Gist options
  • Save Gab0/da50588a952cb22c4a7097b9747e5ff6 to your computer and use it in GitHub Desktop.
Save Gab0/da50588a952cb22c4a7097b9747e5ff6 to your computer and use it in GitHub Desktop.
Gekko Trading Bot - Spawn timeframes inside a strat. So few Indicators can operate on different candlesizes;

This allows to spawn a parallel strat from the root of trading advisor plugin.

*Yeah, for those who saw this earlier: that old method was complicated. Now we are talking about USABILITY :333

replace your plugins/tradingAdvisor/tradingAdvisor.js by the version show here;
look at dfMACD strat for usage;


@plugins/tradingAdvisor/baseTradingMethod.js: you need to comment following lines for this to work.
  //if(this.setup)
   // util.die('Can only add indicators in the init method!');
// internally we only use 1m
// candles, this can easily
// convert them to any desired
// size.
// Acts as ~fake~ stream: takes
// 1m candles as input and emits
// bigger candles.
//
// input are transported candles.
var _ = require('lodash');
var util = require(__dirname + '/util');
var log = require(__dirname + '/log');
var CandleBatcher = function(candleSize) {
if(!_.isNumber(candleSize))
throw 'candleSize is not a number';
this.candleSize = [candleSize];
this.candleCount = 0;
this.smallCandles = [];
_.bindAll(this);
}
util.makeEventEmitter(CandleBatcher);
CandleBatcher.prototype.write = function(candles, Mode) {
if(!_.isArray(candles))
throw 'candles is not an array';
_.each(candles, function(candle) {
this.smallCandles.push(candle);
this.candleCount++;
this.check(Mode);
}, this);
}
CandleBatcher.prototype.check = function(Mode) {
_.each(this.candleSize, function(candleSize) {
var sizeMatch = this.candleCount % candleSize == 0;
if(sizeMatch)
{
var L = this.smallCandles.length;
this.emit('candle'+candleSize,
this.calculate(this.smallCandles.slice(L-candleSize,L)));
}
}, this);
}
CandleBatcher.prototype.calculate = function(Candles) {
var first = Candles.shift();
first.vwp = first.vwp * first.volume;
var candle = _.reduce(
Candles,
function(candle, m) {
candle.high = _.max([candle.high, m.high]);
candle.low = _.min([candle.low, m.low]);
candle.close = m.close;
candle.volume += m.volume;
candle.vwp += m.vwp * m.volume;
candle.trades += m.trades;
return candle;
},
first
);
if(candle.volume)
// we have added up all prices (relative to volume)
// now divide by volume to get the Volume Weighted Price
candle.vwp /= candle.volume;
else
// empty candle
candle.vwp = candle.open;
candle.start = first.start;
return candle;
}
module.exports = CandleBatcher;
/*
MACD - DJM 31/12/2013
(updated a couple of times since, check git history)
*/
// helpers
var _ = require('lodash');
var log = require('../core/log.js');
// let's create our own method
var method = {};
// prepare everything our method needs
method.init = function() {
// keep state about the current trend
// here, on every new candle we use this
// state object to check if we need to
// report it.
this.trend = {
direction: 'none',
duration: 0,
persisted: false,
adviced: false
};
var parametersMACD = {optInFastPeriod: 12, optInSlowPeriod: 26, optInSignalPeriod: 9};
this.requiredHistory = this.tradingAdvisor.historySize;
this.candle32 = this.newTimeFrame(32);
this.candle16 = this.newTimeFrame(16);
this.candleClone = this.newTimeFrame(1);
this.candle32.addIndicator('macd', 'MACD', this.settings);
this.candle16.addIndicator('macd', 'MACD', this.settings);
this.candleClone.addIndicator('macd', 'MACD', this.settings);
log.debug('TimeFrame>', this.candle32);
log.debug('macd@TimeFrame32>', this.candle32.indicators.macd);
log.debug('macd@TimeFrame16>', this.candle16.indicators.macd);
log.debug('macd@TimeFrameClone>', this.candleClone.indicators.macd)
//this.candle32.addTalibIndicator('macd', 'macd', parametersMACD);
// how many candles do we need as a base
// before we can start giving advice?
// define the indicators we need
this.addIndicator('macd', 'MACD', this.settings);
}
// what happens on every new candle?
method.update = function(candle) {
// nothing!
}
// for debugging purposes: log the last calculated
// EMAs and diff.
method.log = function() {
var digits = 8;
var macd = this.indicators.macd;
var diff = macd.diff;
var signal = macd.signal.result;
log.debug('original.macd>', this.indicators.macd.result);
log.debug('candle32.macd>', this.candle32.indicators.macd.result);
//log.debug('candle32.ta-libMACD>', this.candle32.talibIndicators.macd.result);
log.debug('candle16.macd>', this.candle16.indicators.macd.result);
log.debug('candleClone.macd>', this.candleClone.indicators.macd.result);
//log.debug('ta-libMACD>', this.talibIndicators.macd.result);
}
method.check = function() {
var macddiff = this.indicators.macd.result;
if(macddiff > this.settings.thresholds.up && this.candle32.indicators.macd.result > this.settings.thresholds.up) {
// new trend detected
if (this.trend.direction !== 'up')
// reset the state for the new trend
this.trend = {
duration: 0,
persisted: false,
direction: 'up',
adviced: false
};
this.trend.duration++;
if(this.trend.duration >= this.settings.thresholds.persistence)
this.trend.persisted = true;
if(this.trend.persisted && !this.trend.adviced) {
this.trend.adviced = true;
this.advice('long');
} else
this.advice();
} else if(macddiff < this.settings.thresholds.down && this.candle32.indicators.macd.result < this.settings.thresholds.down) {
// new trend detected
if(this.trend.direction !== 'down')
// reset the state for the new trend
this.trend = {
duration: 0,
persisted: false,
direction: 'down',
adviced: false
};
this.trend.duration++;
if(this.trend.duration >= this.settings.thresholds.persistence)
this.trend.persisted = true;
if(this.trend.persisted && !this.trend.adviced) {
this.trend.adviced = true;
this.advice('short');
} else
this.advice();
} else {
// we're not in an up nor in a downtrend
// but for now we ignore sideways trends
//
// read more @link:
//
// https://github.com/askmike/gekko/issues/171
// this.trend = {
// direction: 'none',
// duration: 0,
// persisted: false,
// adviced: false
// };
this.advice();
}
}
module.exports = method;
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 moment = require('moment');
var isLeecher = config.market && config.market.type === 'leech';
var Actor = function(done) {
_.bindAll(this);
this.done = done;
methodName = config.tradingAdvisor.method;
this.method= Actor.prototype.setupTradingMethod(methodName, true);
this.method.batcher.on('candle'+config.tradingAdvisor.candleSize, this.method.tick);
//setup main strategy and main candle batcher;
//this.method = this.setupTradingMethod(methodName, true);
//this.method.batcher = this.setupCandleBatcher(this.method, config.tradingAdvisor.candleSize);
this.method.on('advice', this.relayAdvice);
//this.batcher = this.setupCandleBatcher(this.method, config.tradingAdvisor.candleSize);
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();
}
util.makeEventEmitter(Actor);
Actor.prototype.initConsultant = function(methodName)
{
var method = require(dirs.methods + methodName);
// bind all trading method specific functions
// to the Consultant.
var Consultant = require('./baseTradingMethod');
_.each(method, function(fn, name) {
Consultant.prototype[name] = fn;
});
return Consultant;
}
Actor.prototype.registerCandleMessage = function(method, candleSize)
{
Actor.prototype.method.batcher.candleSize.push(candleSize);
Actor.prototype.method.batcher.on('candle'+candleSize, method.tick);
}
Actor.prototype.setupTradingMethod = function(methodName, isMasterMethod) {
if(!fs.existsSync(dirs.methods + methodName + '.js'))
util.die('Gekko can\'t find the strategy "' + methodName + '"');
log.info('\t', 'Using the strategy: ' + methodName);
var protoMethod = this.initConsultant(methodName);
if (isMasterMethod)//the method that contains its own candle batcher;
{
protoMethod.prototype.batcher = this.setupCandleBatcher(protoMethod, config.tradingAdvisor.candleSize);
protoMethod.prototype.secondaryMethods = [];
protoMethod.prototype.newTimeFrame = function(candleSize)
{
var childMethod = Actor.prototype.setupTradingMethod('noop', false);
//Consultant.prototype.batcher.on('candle'+candleSize, childMethod.tick);
if (candleSize in this.batcher.candleSize === false)
this.batcher.candleSize.push(candleSize);
this.batcher.on('candle'+candleSize, childMethod.tick);
if (this.asyncTick)
{
util.die("Timeframes are not compatiple with TA-lib or Tulind indicators on main method.")
}
//Consultant.prototype.secondaryBatchers.push(
// Actor.prototype.setupCandleBatcher(childMethod, candleSize));
childMethod.setup = false;
childMethod.hasSyncIndicators = true;
childMethod.requiredHistory = 1;
this.secondaryMethods.push(childMethod);
return childMethod;
}
}
if(config[methodName]) {
var tradingSettings = config[methodName];
}
var Method = new protoMethod(tradingSettings);
console.log(">>>>>>>>>>", Method);
return Method;
}
Actor.prototype.setupCandleBatcher = function(method, candleSize) {
var CandleBatcher = require(dirs.core + 'candleBatcher');
var batcher = new CandleBatcher(candleSize);
//batcher.on('candle', method.tick);
return batcher;
}
// HANDLERS
// process the 1m candles
Actor.prototype.processCandle = function(candle, done) {
//write candles to main batcher;
this.method.batcher.write([candle], 'interval');
done();
}
// propogate a custom sized candle to the trading method - DEPRECATED;
Actor.prototype.processCustomCandle = function(candle) {
this.method.tick(candle);
}
// pass through shutdown handler
Actor.prototype.finish = function(done) {
this.method.finish(done);
}
// EMITTERS
Actor.prototype.relayAdvice = function(advice) {
this.emit('advice', advice);
}
module.exports = Actor;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment