gekko
Version:
A bitcoin trading bot for auto trading at various exchanges
173 lines (138 loc) • 5.47 kB
JavaScript
//
// This is an abstract constructor function that has all methods needed to query
// raw trade information from exchanges (through objects described in `/exchanges`)
// and convert them into candles.
//
// Trade methods can extend this constructor to
//
var moment = require('moment');
var _ = require('underscore');
var util = require('../util.js');
var Util = require('util');
var log = require('../log.js');
var EventEmitter = require('events').EventEmitter;
// `abstract` constructor
var CandleCalculator = function() {}
Util.inherits(CandleCalculator, EventEmitter);
// Prepares the candle fetcher and instantiates some objects
// to keep track of what has been fetched.
CandleCalculator.prototype.set = function() {
this.interval = this.settings.interval;
this.candles = this.settings.candles;
this.currentBucket = this.candles - 2;
var list = _.range(this.candles - 1);
// buckets store all trades ordered per candle _chronologically reversed_
this.buckets = _.map(list, function() { return [] });
// candles are stored _chronologically_ (first is old, last is new)
this.candles = {
open: [],
high: [],
low: [],
close: []
}
this.emit('prepared');
}
// calculate [amount] of historical candles based on trades provided by the watcher
//
// This is done by fetching all trades and bucketing all trades in the appropiate
// trade buckets (each candle corresponds to one bucket) and calculating the candle (OHCL)
// for each bucket.
CandleCalculator.prototype.getHistoricalCandles = function() {
log.debug('fetching historical data at', this.watcher.name);
this.fetchingHistorical = true;
var candleStartTime = util.intervalsAgo(this.currentBucket + 2);
this.watcher.getTrades(candleStartTime, this.fillBuckets);
}
// fill buckets based on the trade data.
CandleCalculator.prototype.fillBuckets = function(trades) {
var latestTradeDate;
var startBucket = this.currentBucket;
var nextBucketTime = util.intervalsAgo(this.currentBucket);
_.every(trades, function(trade) {
var time = moment.unix(trade.date);
// if this trade should go to the next bucket
if(time > nextBucketTime) {
// we can calculate the candle of the freshly filled bucket
this.calculateCandle();
this.currentBucket--;
// if we have to fill the most recent bucket we just use
// the fillNewBucket method
if(this.currentBucket === 0) {
this.getNewCandle();
return false;
}
nextBucketTime = util.intervalsAgo(this.currentBucket);
}
latestTradeDate = time;
this.buckets[ this.currentBucket ].push(parseFloat(trade.price));
return true;
}, this);
// we're done if we stuffed all trades in
// all buckets except for the most recent one
if(this.currentBucket < 1)
return;
log.debug('need new trades, refetching', this.watcher.name);
// if we did not got enough trades to fill a single bucket
// we ask as for new trades from the last point
if(startBucket === this.currentBucket)
return this.watcher.getTrades(latestTradeDate.add('s', 1), this.fillBuckets);
var candleStartTime = util.intervalsAgo(this.currentBucket + 1);
this.watcher.getTrades(candleStartTime, this.fillBuckets);
}
// this method fetches most recent trades and calculates a new candle based on the trades
CandleCalculator.prototype.getNewCandle = function() {
if(this.fetchingHistorical)
// we just fetched all historical buckets
this.fetchingHistorical = false;
else
this.removeOldestBucket();
this.removeOldestCandle();
log.debug('fetching new trades for new bucket at', this.watcher.name);
this.watcher.getTrades(false, this.fillNewBucket, true);
}
CandleCalculator.prototype.removeOldestBucket = function() {
// buckets are stored reversed chronologically
this.buckets.pop();
}
CandleCalculator.prototype.removeOldestCandle = function() {
// candles are stored chronologically
this.candles.open.shift();
this.candles.high.shift();
this.candles.low.shift();
this.candles.close.shift();
}
CandleCalculator.prototype.fillNewBucket = function(trades) {
var candleStartTime = util.intervalsAgo(1);
this.buckets.splice(0, 0, []);
// for as long as trades fit into the new bucket
// put them in.
_.every(trades, function(trade) {
var time = moment.unix(trade.date);
if(time < candleStartTime)
// this trade belongs to the previous bucket
// skip everything from here on
return false;
this.buckets[0].push(parseFloat(trade.price));
return true;
}, this);
if(!this.buckets[0].length) {
// none of the recieved trades were after the previous candle
log.debug('did not receive trades for most recent candle, retrying..');
return setTimeout(this.getNewCandle, +moment.duration(10, 'seconds'));
}
this.calculateCandle();
}
CandleCalculator.prototype.calculateCandle = function() {
var bucket = this.buckets[this.currentBucket];
// because buckets (and their contents) are chronologically reversed
// the _last_ item is the open and the _first_ is the close
this.candles.open.push(_.last(bucket));
this.candles.high.push(_.max(bucket));
this.candles.low.push(_.min(bucket));
this.candles.close.push(_.first(bucket));
log.debug('calculated candle:', this.currentBucket);
this.emit('calculated candle');
if(this.currentBucket === 0)
this.emit('calculated new candle');
}
module.exports = CandleCalculator;