Skip to content
This repository has been archived by the owner on Feb 15, 2022. It is now read-only.

Enhance backtester: support all strageties and collect vs buy hold value #344

Merged
merged 3 commits into from
Jul 3, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 116 additions & 22 deletions scripts/auto_backtester/backtester.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ let NEUTRAL_RATE_AUTO = false

let countArr = [];

let range = (start, end) => {
return Array(end - start + 1).fill().map((_, idx) => start + idx)
let range = (start, end, step) => {
if (!step) step = 1;
var r = [];
for (i=start; i<=end; i+=step) {
r = r.concat(i);
}
return r;
};

let product = args => {
Expand Down Expand Up @@ -66,8 +71,15 @@ let objectProduct = obj => {

let runCommand = (strategy, cb) => {
countArr.push(1);
let command = `zenbot sim ${simArgs} --trend_ema=${strategy.trend_ema} --oversold_rsi=${strategy.oversold_rsi} --oversold_rsi_periods=${strategy.oversold_rsi_periods} --neutral_rate=${strategy.neutral_rate}`;
console.log(`[ ${countArr.length}/${strategies.length} ] ${command}`);
let strategyArgs = {
macd: `--ema_short_period=${strategy.ema_short_period} --ema_long_period=${strategy.ema_long_period} --signal_period=${strategy.signal_period} --up_trend_threshold=${strategy.up_trend_threshold} --down_trend_threshold=${strategy.down_trend_threshold} --overbought_rsi_periods=${strategy.overbought_rsi_periods} --overbought_rsi=${strategy.overbought_rsi}`,
rsi: `--rsi_periods=${strategy.rsi_periods} --oversold_rsi=${strategy.oversold_rsi} --overbought_rsi=${strategy.overbought_rsi} --rsi_recover=${strategy.rsi_recover} --rsi_drop=${strategy.rsi_drop} --rsi_divisor=${strategy.rsi_divisor}`,
sar: `--sar_af=${strategy.sar_af} --sar_max_af=${strategy.sar_max_af}`,
speed: `--baseline_periods=${strategy.baseline_periods} --trigger_factor=${strategy.trigger_factor}`,
trend_ema: `--trend_ema=${strategy.trend_ema} --oversold_rsi=${strategy.oversold_rsi} --oversold_rsi_periods=${strategy.oversold_rsi_periods} --neutral_rate=${strategy.neutral_rate}`
};
let command = `node zenbot sim ${simArgs} ${strategyArgs[strategyName]}`;
console.log(`[ ${countArr.length}/${strategies[strategyName].length} ] ${command}`);

shell.exec(command, {silent:true, async:true}, (code, stdout, stderr) => {
if (code) {
Expand All @@ -82,14 +94,18 @@ let runCommand = (strategy, cb) => {
let processOutput = output => {
let jsonRegexp = /(\{[\s\S]*?\})\send balance/g;
let endBalRegexp = /end balance: (\d+\.\d+) \(/g;
let buyHoldRegexp = /buy hold: (\d+\.\d+) \(/g;
let vsBuyHoldRegexp = /vs. buy hold: (-?\d+\.\d+)%/g;
let wlRegexp = /win\/loss: (\d+)\/(\d+)/g;
let errRegexp = /error rate: (.*)%/g;

let output2 = output.substr(output.length - 3000);

let rawParams = jsonRegexp.exec(output2)[1];
let params = JSON.parse(rawParams);
let endBalance = endBalRegexp.exec(output2)[1];
let buyHold = buyHoldRegexp.exec(output2)[1];
let vsBuyHold = vsBuyHoldRegexp.exec(output2)[1];
let wlMatch = wlRegexp.exec(output2);
let wins = parseInt(wlMatch[1]);
let losses = parseInt(wlMatch[2]);
Expand All @@ -104,13 +120,43 @@ let processOutput = output => {
return {
params: rawParams.replace(/[\r\n]/g, ''),
endBalance: parseFloat(endBalance),
buyHold: parseFloat(buyHold),
vsBuyHold: parseFloat(vsBuyHold),
wins: wins,
losses: losses,
errorRate: parseFloat(errorRate),
trendEma: params.trend_ema,

// macd
emaShortPeriod: params.ema_short_period,
emaLongPeriod: params.ema_long_period,
signalPeriod: params.signal_period,
upTrendThreshold: params.up_trend_threshold,
downTrendThreshold: params.down_trend_threshold,
overboughtRsiPeriods: params.overbought_rsi_periods,
overboughtRsi: params.overbought_rsi,

// rsi
rsiPeriods: params.rsi_periods,
oversoldRsi: params.oversold_rsi,
oversoldRsiPeriods: params.oversold_rsi_periods,
overboughtRsi: params.overbought_rsi,
rsiRecover: params.rsi_recover,
rsiDrop: params.rsi_drop,
rsiDivsor: params.rsi_divisor,

// sar
sarAf: params.sar_af,
sarMaxAf: params.sar_max_af,

// speed
baselinePeriods: params.baseline_periods,
triggerFactor: params.trigger_factor,

// trend_ema
trendEma: params.trend_ema,
neutralRate: params.neutral_rate,
oversoldRsiPeriods: params.oversold_rsi_periods,
oversoldRsi: params.oversold_rsi,

days: days,
period: params.period,
roi: roi,
Expand All @@ -119,27 +165,59 @@ let processOutput = output => {
};
};

let strategies = objectProduct({
trend_ema: range(TREND_EMA_MIN, TREND_EMA_MAX),
oversold_rsi: range(OVERSOLD_RSI_MIN, OVERSOLD_RSI_MAX),
oversold_rsi_periods: range(OVERSOLD_RSI_PERIODS_MIN, OVERSOLD_RSI_PERIODS_MAX),
neutral_rate: (NEUTRAL_RATE_AUTO ? new Array('auto') : []).concat(range(NEUTRAL_RATE_MIN, NEUTRAL_RATE_MAX).map(r => r / 100))
});

let tasks = strategies.map(strategy => {
return cb => {
runCommand(strategy, cb)
}
});
let strategies = {
macd: objectProduct({
ema_short_period: range(10, 15),
ema_long_period: range(20, 30),
signal_period: range(9, 9),
up_trend_threshold: range(0, 0),
down_trend_threshold: range(0, 0),
overbought_rsi_periods: range(15, 25),
overbought_rsi: range(70, 70)
}),
rsi: objectProduct({
rsi_periods: range(10, 30),
oversold_rsi: range(20, 35),
overbought_rsi: range(82, 82),
rsi_recover: range(3, 3),
rsi_drop: range(0, 0),
rsi_divisor: range(2, 2)
}),
sar: objectProduct({
sar_af: range(0.01, 0.055, 0.005),
sar_max_af: range(0.1, 0.55, 0.05)
}),
speed: objectProduct({
baseline_periods: range(1000, 5000, 200),
trigger_factor: range(1.0, 2.0, 0.1)
}),
trend_ema: objectProduct({
trend_ema: range(TREND_EMA_MIN, TREND_EMA_MAX),
neutral_rate: (NEUTRAL_RATE_AUTO ? new Array('auto') : []).concat(range(NEUTRAL_RATE_MIN, NEUTRAL_RATE_MAX).map(r => r / 100)),
oversold_rsi_periods: range(OVERSOLD_RSI_PERIODS_MIN, OVERSOLD_RSI_PERIODS_MAX),
oversold_rsi: range(OVERSOLD_RSI_MIN, OVERSOLD_RSI_MAX)
})
};

let args = process.argv;
args.shift();
args.shift();
let simArgs = args.join(' ');
let strategyName = 'trend_ema';
if (args.indexOf('--strategy') !== -1) {
strategyName = args[args.indexOf('--strategy') + 1];
}

// let tasks = strategies_tend_ema.map(strategy => {
let tasks = strategies[strategyName].map(strategy => {
return cb => {
runCommand(strategy, cb)
}
});

console.log(`\n--==${VERSION}==--`);
console.log(new Date().toUTCString());
console.log(`\nBacktesting [${strategies.length}] iterations...\n`);
console.log(`\nBacktesting [${strategies[strategyName].length}] iterations for strategy ${strategyName}...\n`);

parallel(tasks, PARALLEL_LIMIT, (err, results) => {
console.log("\nBacktesting complete, saving results...");
Expand All @@ -148,10 +226,26 @@ parallel(tasks, PARALLEL_LIMIT, (err, results) => {
})
results.sort((a,b) => (a.roi < b.roi) ? 1 : ((b.roi < a.roi) ? -1 : 0));
let fileName = `backtesting_${Math.round(+new Date()/1000)}.csv`;
let filedsGeneral = ['roi', 'vsBuyHold', 'errorRate', 'wlRatio', 'frequency', 'endBalance', 'buyHold', 'wins', 'losses', 'period', 'days'];
let filedNamesGeneral = ['ROI (%)', 'VS Buy Hold (%)', 'Error Rate (%)', 'Win/Loss Ratio', '# Trades/Day', 'Ending Balance ($)', 'Buy Hold ($)', '# Wins', '# Losses', 'Period', '# Days'];
let fields = {
macd: filedsGeneral.concat([ 'emaShortPeriod', 'emaLongPeriod', 'signalPeriod', 'upTrendThreshold', 'downTrendThreshold', 'overboughtRsiPeriods', 'overboughtRsi', 'params']),
rsi: filedsGeneral.concat(['rsiPeriods', 'oversoldRsi', 'overboughtRsi', 'rsiRecover', 'rsiDrop', 'rsiDivsor', 'params']),
sar: filedsGeneral.concat(['sarAf', 'sarMaxAf', 'params']),
speed: filedsGeneral.concat(['baselinePeriods', 'triggerFactor', 'params']),
trend_ema: filedsGeneral.concat(['trendEma', 'neutralRate', 'oversoldRsiPeriods', 'oversoldRsi', 'params'])
};
let fieldNames = {
macd: filedNamesGeneral.concat(['EMA Short Period', 'EMA Long Period', 'Signal Period', 'Up Trend Threshold', 'Down Trend Threshold', 'Overbought Rsi Periods', 'Overbought Rsi', 'Full Parameters']),
rsi: filedNamesGeneral.concat(['RSI Periods', 'Oversold RSI', 'Overbought RSI', 'RSI Recover', 'RSI Drop', 'RSI Divisor', 'Full Parameters']),
sar: filedNamesGeneral.concat(['SAR AF', 'SAR MAX AF', 'Full Parameters']),
speed: filedNamesGeneral.concat(['Baseline Periods', 'Trigger Factor', 'Full Parameters']),
trend_ema: filedNamesGeneral.concat(['Trend EMA', 'Neutral Rate', 'Oversold RSI Periods', 'Oversold RSI', 'Full Parameters'])
};
let csv = json2csv({
data: results,
fields: ['roi', 'errorRate', 'wlRatio', 'frequency', 'endBalance', 'wins', 'losses', 'period', 'days', 'trendEma', 'oversoldRsi', 'oversoldRsiPeriods', 'neutralRate', 'params'],
fieldNames: ['ROI (%)', 'Error Rate (%)', 'Win/Loss Ratio', '# Trades/Day', 'Ending Balance ($)', '# Wins', '# Losses', 'Period', '# Days', 'Trend EMA', 'Oversold RSI', 'Oversold RSI Periods', 'Neutral Rate', 'Full Parameters']
fields: fields[strategyName],
fieldNames: fieldNames[strategyName]
});

fs.writeFile(fileName, csv, err => {
Expand Down