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/core/tools/dataStitcher.js
2021-06-06 20:25:14 +02:00

234 lines
7.3 KiB
JavaScript

var _ = require('lodash');
var fs = require('fs');
var moment = require('moment');
var util = require('../util');
var config = util.getConfig();
var dirs = util.dirs();
var log = require(dirs.core + '/log');
var Stitcher = function(batcher) {
this.batcher = batcher;
}
Stitcher.prototype.ago = function(ts) {
var now = moment().utc();
var then = moment.unix(ts).utc();
return now.diff(then, 'minutes') + ' minutes ago';
}
Stitcher.prototype.verifyExchange = function() {
require(dirs.gekko + 'exchange/dependencyCheck');
const exchangeChecker = require(dirs.gekko + 'exchange/exchangeChecker');
const slug = config.watch.exchange.toLowerCase();
let exchange;
try {
exchange = exchangeChecker.getExchangeCapabilities(slug);
} catch(e) {
util.die(e.message);
}
if(!exchange)
util.die(`Unsupported exchange: ${slug}`);
var error = exchangeChecker.cantMonitor(config.watch);
if(error)
util.die(error, true);
}
Stitcher.prototype.prepareHistoricalData = function(done) {
this.verifyExchange();
// - step 1: check most recent stored candle window
// - step 2: check oldest trade reachable by API
// - step 3: see if overlap
// - step 4: feed candle stream into CandleBatcher
if(config.tradingAdvisor.historySize === 0)
return done();
var requiredHistory = config.tradingAdvisor.candleSize * config.tradingAdvisor.historySize;
var Reader = require(dirs.plugins + config.adapter + '/reader');
this.reader = new Reader;
log.info(
'\tThe trading method requests',
requiredHistory,
'minutes of historic data. Checking availablity..'
);
var endTime = moment().utc().startOf('minute');
var idealStartTime = endTime.clone().subtract(requiredHistory, 'm');
this.reader.mostRecentWindow(idealStartTime, endTime, function(localData) {
// now we know what data is locally available, what
// data would we need from the exchange?
if(!localData) {
log.info('\tNo usable local data available, trying to get as much as possible from the exchange..');
var idealExchangeStartTime = idealStartTime.clone();
var idealExchangeStartTimeTS = idealExchangeStartTime.unix();
}
else if (idealStartTime.unix() < localData.from) {
log.info('\tLocal data is still too recent, trying to get as much as possible from the exchange');
var idealExchangeStartTime = idealStartTime.clone();
var idealExchangeStartTimeTS = idealExchangeStartTime.unix();
}
else {
log.debug('\tAvailable local data:');
log.debug('\t\tfrom:', this.ago(localData.from));
log.debug('\t\tto:', this.ago(localData.to));
log.info('\tUsable local data available, trying to match with exchange data..')
// local data is available, we need the next minute
// make sure we grab back in history far enough
var secondsOverlap = 60 * 15; // 15 minutes
var idealExchangeStartTimeTS = localData.to - secondsOverlap;
var idealExchangeStartTime = moment.unix(idealExchangeStartTimeTS).utc();
// already set the
util.setConfigProperty(
'tradingAdvisor',
'firstFetchSince',
idealExchangeStartTimeTS
);
}
// Limit the history Gekko can try to get from the exchange.
var minutesAgo = endTime.diff(idealExchangeStartTime, 'minutes');
var maxMinutesAgo = 4 * 60; // 4 hours
if(minutesAgo > maxMinutesAgo) {
log.info('\tPreventing Gekko from requesting', minutesAgo, 'minutes of history.');
idealExchangeStartTime = endTime.clone().subtract(maxMinutesAgo, 'minutes');
idealExchangeStartTimeTS = idealExchangeStartTime.unix();
}
log.debug('\tFetching exchange data since', this.ago(idealExchangeStartTimeTS))
this.checkExchangeTrades(idealExchangeStartTime, function(err, exchangeData) {
log.debug('\tAvailable exchange data:');
log.debug('\t\tfrom:', this.ago(exchangeData.from));
log.debug('\t\tto:', this.ago(exchangeData.to));
// in case we have limited local data, and the
// exchange would offer more than we have: ignore
// the local data..
if(
localData &&
exchangeData &&
exchangeData.from < localData.from
) {
log.debug('\tExchange offered more data than locally available. Ignoring local data');
localData = false;
}
var stitchable = localData && exchangeData.from <= localData.to;
if(stitchable) {
log.debug('\tStitching datasets');
// we can combine local data with exchange data
if(idealStartTime.unix() >= localData.from) {
log.info(
'\tFull history locally available.',
'Seeding the trading method with all required historical candles.'
);
} else {
// stitchable but not enough
log.info(
'\tPartial history locally available, but',
Math.round((localData.from - idealStartTime.unix()) / 60),
'minutes are missing.')
log.info('\tSeeding the trading method with',
'partial historical data (Gekko needs more time before',
'it can give advice).'
);
}
// seed all historic data up to the point the exchange can provide.
var from = localData.from;
var to = moment.unix(exchangeData.from).utc()
.startOf('minute')
.subtract(1, 'minute')
.unix();
log.debug('\tSeeding with:');
log.debug('\t\tfrom:', this.ago(from));
log.debug('\t\tto:', this.ago(to));
return this.seedLocalData(from, to, done);
} else if(!stitchable) {
log.debug('\tUnable to stitch datasets.')
// we cannot use any local data..
log.info(
'\tNot seeding locally available data to the trading method.'
);
if(exchangeData.from < idealExchangeStartTimeTS) {
log.info('\tHowever the exchange returned enough data anyway!');
} else if(localData) {
log.info(
'\tThe exchange does not return enough data.',
Math.round((localData.from - idealStartTime.unix()) / 60),
'minutes are still missing.'
);
}
}
done();
}.bind(this));
}.bind(this));
}
Stitcher.prototype.checkExchangeTrades = function(since, next) {
var provider = config.watch.exchange.toLowerCase();
var DataProvider = require(util.dirs().gekko + 'exchange/wrappers/' + provider);
var exchangeConfig = config.watch;
// include trader config if trading is enabled
if (_.isObject(config.trader) && config.trader.enabled) {
exchangeConfig = _.extend(config.watch, config.trader);
}
var watcher = new DataProvider(exchangeConfig);
watcher.getTrades(since, function(e, d) {
if(e) {
util.die(e.message);
}
if(_.isEmpty(d))
return util.die(
`Gekko tried to retrieve data since ${since.format('YYYY-MM-DD HH:mm:ss')}, however
${provider} did not return any trades.`
);
next(e, {
from: _.first(d).date,
to: _.last(d).date
})
});
}
Stitcher.prototype.seedLocalData = function(from, to, next) {
this.reader.get(from, to, 'full', function(err, rows) {
rows = _.map(rows, row => {
row.start = moment.unix(row.start);
return row;
});
this.batcher.write(rows);
this.batcher.flush();
this.reader.close();
next();
}.bind(this));
}
module.exports = Stitcher;