diff --git a/scripts/auto_backtester/backtester.js b/scripts/auto_backtester/backtester.js index 023607a8e8..31fb23f183 100755 --- a/scripts/auto_backtester/backtester.js +++ b/scripts/auto_backtester/backtester.js @@ -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 => { @@ -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) { @@ -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]); @@ -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, @@ -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..."); @@ -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 => {