Skip to content

Commit

Permalink
added backtesting 0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
askmike committed Jun 30, 2013
1 parent 8b5239d commit 9341a9c
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 43 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ dwsync.xml

# Folders to ignore

node_modules
node_modules
candles.csv
21 changes: 20 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ config.EMA = {
buyTreshold: 0.25
};

// Monitor the live market
config.normal = {
enabled: true,
exchange: 'MtGox', // 'MtGox', 'BTCe' or 'Bitstamp'
Expand Down Expand Up @@ -76,13 +77,31 @@ config.profitCalculator = {
currency: 100,
},
// only want report after a sell? set to `false`.
verbose: true
verbose: false,
// how much fee in % does each trade cost?
fee: 0.6
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ADVANCED ZONE
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Backtesting strategies against historical data
//
// Test a strategy on historical data
//
// Read here: https://github.com/askmike/gekko/blob/master/docs/Backtesting.md
//
// NOTE: THIS FEATURE HAS NOT BEEN PROPERELY TESTED YET, IT IS NOT
// ADVISED TO MAKE REAL WORLD DECISIONS BASED ON THE RESULTS
// UNTIL THE CODE HAS BEEN PROVED SOLID.
config.backtest = {
enabled: false,
candleFile: 'candles.csv',
from: 0,
to: 0
}

// For when you want to monitor a market but want to act (trade) on a different one
// (or different ones).
//
Expand Down
29 changes: 29 additions & 0 deletions docs/Backtesting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Backtesting with Gekko

**Note that this functionality should only be used for testing purposes at this moment as it's in early development stage**

## Preparing Gekko

You can configure Gekko to test the current EMA strategy on historical data. To do this you need candle data in CSV format. On [this webpage](https://bitcointalk.org/index.php?topic=239815.0) you can downloaded precalculated candles from Mt. Gox or you can calculate your own using the script provided in the link. Alternatively you can supply your own candles, the only requirement is that the csv file has candles ordered like this: `timestamp,open,high,low,close`.

## Configuring Gekko

Once you have the csv file with candles you can configure Gekko for backtesting: in [config.js](https://github.com/askmike/gekko/blob/master/config.js) in the advanced zone you need to the backtesting part like so:

config.backtest = {
enabled: true, // set to true to run backtests instead of live monitoring
candleFile: 'candles.csv', // the candles file
from: 0, // optional start timestamp
to: 0 // optional end timestamp
}

Once configured Gekko will run the backtest instead of watching the live market. It wil use the following configuration items:

* Everything under `backtest`.
* Everything under `profitCalculator`.
* Everything under `EMA` with the exception of interval, as this will be determined by the candles file.

## Notes

* Use the backtesting feature only for testing until the code is stable.
* When there are missing candles Gekko will act as if the whole duration of the missing candle never happened.
46 changes: 39 additions & 7 deletions gekko.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,41 @@ var async = require('async');
var Manager = require('./portfolioManager');

var config = util.getConfig();
var Consultant = require('./methods/' + config.tradingMethod.toLowerCase().split(' ').join('-'));

log.info('I\'m gonna make you rich, Bud Fox.');
log.info('Let me show you some ' + config.tradingMethod + '.\n\n');

if(config.backtest.enabled) {
log.info('Preparing backtester to test strategy against historical data.');

// implement a trading method to create a consultant.
var consultant = new Consultant();

// overwrite the watcher in case of normal setup
if(config.normal.enabled)
config.watch = config.normal;

var Logger = require('./logger');
var logger = new Logger(_.extend(config.profitCalculator, config.watch));
consultant.on('advice', logger.inform);
if(config.profitCalculator.enabled)
consultant.on('advice', logger.trackProfits);

consultant.on('finish', logger.finish);

consultant.emit('prepare');
return;
}


//
// Normalize the configuration between normal & advanced
//
if(config.normal && config.normal.enabled) {
// if the normal settings are enabled we overwrite the
// watcher and traders set in the advanced zone
log.info('Using normal settings');
log.info('Using normal settings to monitor the live market');
config.watch = config.normal;
config.traders = [];

Expand All @@ -47,8 +73,9 @@ if(config.normal && config.normal.enabled) {
log.info('Using advanced settings');
}


// create a public exchange object which can retrieve trade information
//
// create a public exchange object which can retrieve live trade information
//
var provider = config.watch.exchange.toLowerCase();
if(provider === 'btce') {
// we can't fetch historical data from btce directly so we use bitcoincharts
Expand All @@ -61,7 +88,6 @@ var watcher = new DataProvider(config.watch);

// implement a trading method to create a consultant, we pass it a config and a
// public mtgox object which the method can use to get data on past trades
var Consultant = require('./methods/' + config.tradingMethod.toLowerCase().split(' ').join('-'));
var consultant = new Consultant(watcher);

// log advice
Expand All @@ -71,9 +97,11 @@ consultant.on('advice', logger.inform);
if(config.profitCalculator.enabled)
consultant.on('advice', logger.trackProfits);

// automatically trade
//
// Configure automatic traders based on advice
//
var managers = _.filter(config.traders, function(t) { return t.enabled });
var configureManagers = function(_next) {
var managers = _.filter(config.traders, function(t) { return t.enabled });
var next = _.after(managers.length, _next);
_.each(managers, function(conf) {
conf.exchange = conf.exchange.toLowerCase();
Expand All @@ -84,6 +112,10 @@ var configureManagers = function(_next) {
});
}


//
// Configure automatic email on advice
//
var configureMail = function(next) {
if(config.mail.enabled && config.mail.email) {
var mailer = require('./mailer');
Expand All @@ -96,7 +128,7 @@ var configureMail = function(next) {
}

var start = function() {
consultant.emit('start');
consultant.emit('prepare');
}

async.series([configureMail, configureManagers], start);
51 changes: 46 additions & 5 deletions logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ var _ = require('underscore');
var log = require('./log.js');

var Logger = function(config) {
this.config = util.getConfig();
this.verbose = config.verbose;
this.fee = 1 - config.fee / 100;

this.reportInCurrency = config.reportInCurrency;
if(this.reportInCurrency)
this.reportIn = config.currency;
Expand All @@ -28,14 +31,27 @@ var Logger = function(config) {

if(config.enabled)
log.info('Profit reporter active on simulated balance');

}

// log advice
Logger.prototype.inform = function(what, price, meta) {
if(!this.verbose && what !== 'SELL' && !this.config.backtest)
return;

if(!this.verbose && what === 'HOLD' && this.config.backtest)
return;

log.info('ADVICE is to', what, meta);
}

Logger.prototype.extractFee = function(amount) {
amount *= 100000000;
amount *= this.fee;
amount = Math.floor(amount);
amount /= 100000000;
return amount;
}

// after every succesfull trend ride we end up with more BTC than we started with,
// this function calculates Gekko's profit in %.
Logger.prototype.trackProfits = function(what, price, meta) {
Expand All @@ -51,14 +67,14 @@ Logger.prototype.trackProfits = function(what, price, meta) {

// virtually trade all USD to BTC at the current MtGox price
if(what === 'BUY') {
this.current.asset += this.current.currency / price;
this.current.asset += this.extractFee(this.current.currency / price);
this.current.currency = 0;
this.trades++;
}

// virtually trade all BTC to USD at the current MtGox price
if(what === 'SELL') {
this.current.currency += this.current.asset * price;
this.current.currency += this.extractFee(this.current.asset * price);
this.current.asset = 0;
this.trades++;
}
Expand All @@ -74,12 +90,37 @@ Logger.prototype.trackProfits = function(what, price, meta) {
if(this.tracks === 1)
return;

if(!this.verbose && what === 'SELL')
if(!this.verbose && what === 'SELL' && !this.config.backtest)
this.report();
else if(this.verbose)
else if(this.verbose && !this.config.backtest)
this.report();
}

Logger.prototype.finish = function(data) {
console.log();
console.log();

log.info('\t\tWARNING: BACKTESTING FEATURE NEEDS PROPER TESTING')
log.info('\t\tWARNING: ACT ON THESE NUMBERS AT YOUR OWN RISK!')

console.log();
console.log();

log.info(
'(PROFIT REPORT)',
'start price:\t\t\t',
data.start
);

log.info(
'(PROFIT REPORT)',
'end price:\t\t\t',
data.end
);

this.report();
}

Logger.prototype.report = function() {
var round = function(amount) {
return amount.toFixed(3);
Expand Down
50 changes: 34 additions & 16 deletions methods/exponential-moving-averages.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,62 @@
@link https://bitcointalk.org/index.php?topic=60501.0
*/

var CandleMethod = require('./candle-method.js');

// helpers
var moment = require('moment');
var _ = require('underscore');
var util = require('../util.js');
var Util = require('util');
var log = require('../log.js');

var config = util.getConfig();
var EMAsettings = config.EMA;
var settings = config.EMA;
if(config.backtest.enabled)
settings = _.extend(settings, config.backtest);

var TradingMethod = function(watcher) {
this.watcher = watcher;
this.currentTrend;
this.amount = EMAsettings.ticks + 1;
this.amount = settings.ticks + 1;
// store EMAsettings
this.settings = settings;
// store whole config
this.config = config;

_.bindAll(this);

this.on('start', this.start);
this.on('prepare', this.prepare);
this.on('prepared', this.start);
this.on('calculated candle', this.calculateEMAs);
}

if(config.backtest.enabled)
var CandleMethod = require('./historical-candle-fetcher.js');
else
var CandleMethod = require('./realtime-candle-fetcher.js');
Util.inherits(TradingMethod, CandleMethod);

TradingMethod.prototype.start = function() {
// first prepare this trading method, then
// prepare the candleMethod this trade method
// is extending.
TradingMethod.prototype.prepare = function() {
log.info('Calculating EMA on historical data...');

this.set(EMAsettings, this.watcher);

// setup method specific parameters
this.ema = {
short: [],
long: [],
diff: []
};

this.set();
}

this.on('calculated candle', this.calculateEMAs);
this.getHistoricalCandles();
setInterval(this.getNewCandle, util.minToMs( EMAsettings.interval) );
TradingMethod.prototype.start = function() {
if(config.backtest.enabled) {
this.on('calculated new candle', this.getNewCandle);
this.getHistoricalCandles();
} else {
this.getHistoricalCandles();
setInterval(this.getNewCandle, util.minToMs( settings.interval ) );
}
}

// add a price and calculate the EMAs and
Expand Down Expand Up @@ -83,7 +99,7 @@ TradingMethod.prototype.calculateEMAs = function() {
TradingMethod.prototype.calculateEMA = function(type) {
var price = _.last(this.candles.close);

var k = 2 / (EMAsettings[type] + 1);
var k = 2 / (settings[type] + 1);
var ema, y;

var current = _.size(this.candles.close);
Expand Down Expand Up @@ -111,8 +127,10 @@ TradingMethod.prototype.advice = function() {
var diff = _.last(this.ema.diff).toFixed(3);
var price = _.last(this.candles.close).toFixed(3);
var message = '@ ' + price + ' (' + diff + ')';
if(config.backtest.enabled)
message += '\tat \t' + moment.unix(this.currentTimestamp).format('YYYY-MM-DD HH:mm:ss');

if(diff > EMAsettings.buyTreshold) {
if(diff > settings.buyTreshold) {
log.debug('we are currently in uptrend (' + diff + ')');

if(this.currentTrend !== 'up') {
Expand All @@ -121,7 +139,7 @@ TradingMethod.prototype.advice = function() {
} else
this.emit('advice', 'HOLD', price, message);

} else if(diff < EMAsettings.sellTreshold) {
} else if(diff < settings.sellTreshold) {
log.debug('we are currently in a downtrend', message);

if(this.currentTrend !== 'down') {
Expand Down
Loading

0 comments on commit 9341a9c

Please sign in to comment.