From 27769f03018f5e0606dac448cf4cfcce32da59dc Mon Sep 17 00:00:00 2001 From: kryofly Date: Thu, 11 Jan 2018 17:45:41 +0100 Subject: [PATCH] uncomplex backtest --- freqtrade/optimize/backtesting.py | 116 ++++++++++--------- freqtrade/optimize/hyperopt.py | 4 +- freqtrade/tests/optimize/test_backtesting.py | 23 ++-- 3 files changed, 83 insertions(+), 60 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 315f960d8..8296dc900 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -68,17 +68,59 @@ def generate_text_table( return tabulate(tabular_data, headers=headers, floatfmt=floatfmt) -def backtest(stake_amount: float, processed: Dict[str, DataFrame], - max_open_trades: int = 0, realistic: bool = True, sell_profit_only: bool = False, - stoploss: int = -1.00, use_sell_signal: bool = False) -> DataFrame: +def get_trade_entry(pair, row, ticker, trade_count_lock, args): + stake_amount = args['stake_amount'] + max_open_trades = args.get('max_open_trades', 0) + sell_profit_only = args.get('sell_profit_only', False) + stoploss = args.get('stoploss', -1) + use_sell_signal = args.get('use_sell_signal', False) + trade = Trade(open_rate=row.close, + open_date=row.date, + stake_amount=stake_amount, + amount=stake_amount / row.open, + fee=exchange.get_fee() + ) + + # calculate win/lose forwards from buy point + sell_subset = ticker[row.Index + 1:][['close', 'date', 'sell']] + for row2 in sell_subset.itertuples(index=True): + if max_open_trades > 0: + # Increase trade_count_lock for every iteration + trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1 + + current_profit_percent = trade.calc_profit_percent(rate=row2.close) + if (sell_profit_only and current_profit_percent < 0): + continue + if min_roi_reached(trade, row2.close, row2.date) or \ + (row2.sell == 1 and use_sell_signal) or \ + current_profit_percent <= stoploss: + current_profit_btc = trade.calc_profit(rate=row2.close) + + return row2.Index, (pair, + current_profit_percent, + current_profit_btc, + row2.Index - row.Index, + current_profit_btc > 0, + current_profit_btc < 0 + ) + + +def backtest(args) -> DataFrame: """ Implements backtesting functionality - :param stake_amount: btc amount to use for each trade - :param processed: a processed dictionary with format {pair, data} - :param max_open_trades: maximum number of concurrent trades (default: 0, disabled) - :param realistic: do we try to simulate realistic trades? (default: True) + :param args: a dict containing: + stake_amount: btc amount to use for each trade + processed: a processed dictionary with format {pair, data} + max_open_trades: maximum number of concurrent trades (default: 0, disabled) + realistic: do we try to simulate realistic trades? (default: True) + sell_profit_only: sell if profit only + use_sell_signal: act on sell-signal + stoploss: use stoploss :return: DataFrame """ + processed = args['processed'] + max_open_trades = args.get('max_open_trades', 0) + realistic = args.get('realistic', True) trades = [] trade_count_lock: dict = {} exchange._API = Bittrex({'key': '', 'secret': ''}) @@ -101,41 +143,11 @@ def backtest(stake_amount: float, processed: Dict[str, DataFrame], # Increase lock trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1 - trade = Trade( - open_rate=row.close, - open_date=row.date, - stake_amount=stake_amount, - amount=stake_amount / row.open, - fee=exchange.get_fee() - ) - - # calculate win/lose forwards from buy point - sell_subset = ticker[row.Index + 1:][['close', 'date', 'sell']] - for row2 in sell_subset.itertuples(index=True): - if max_open_trades > 0: - # Increase trade_count_lock for every iteration - trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1 - - current_profit_percent = trade.calc_profit_percent(rate=row2.close) - if (sell_profit_only and current_profit_percent < 0): - continue - if min_roi_reached(trade, row2.close, row2.date) or \ - (row2.sell == 1 and use_sell_signal) or \ - current_profit_percent <= stoploss: - current_profit_btc = trade.calc_profit(rate=row2.close) - lock_pair_until = row2.Index - - trades.append( - ( - pair, - current_profit_percent, - current_profit_btc, - row2.Index - row.Index, - current_profit_btc > 0, - current_profit_btc < 0 - ) - ) - break + ret = get_trade_entry(pair, row, ticker, + trade_count_lock, args) + if ret: + lock_pair_until, trade_entry = ret + trades.append(trade_entry) labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'profit', 'loss'] return DataFrame.from_records(trades, columns=labels) @@ -181,17 +193,17 @@ def start(args): # Print timeframe min_date, max_date = get_timeframe(preprocessed) logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) - # Execute backtest and print results - results = backtest( - stake_amount=config['stake_amount'], - processed=preprocessed, - max_open_trades=max_open_trades, - realistic=args.realistic_simulation, - sell_profit_only=config.get('experimental', {}).get('sell_profit_only', False), - stoploss=config.get('stoploss'), - use_sell_signal=config.get('experimental', {}).get('use_sell_signal', False) - ) + sell_profit_only = config.get('experimental', {}).get('sell_profit_only', False) + use_sell_signal = config.get('experimental', {}).get('use_sell_signal', False) + results = backtest({'stake_amount': config['stake_amount'], + 'processed': preprocessed, + 'max_open_trades': max_open_trades, + 'realistic': args.realistic_simulation, + 'sell_profit_only': sell_profit_only, + 'use_sell_signal': use_sell_signal, + 'stoploss': config.get('stoploss') + }) logger.info( '\n==================================== BACKTESTING REPORT ====================================\n%s', # noqa generate_text_table(data, results, config['stake_currency'], args.ticker_interval) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index cf46b96ad..146874b9c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -128,7 +128,9 @@ def optimizer(params): from freqtrade.optimize import backtesting backtesting.populate_buy_trend = buy_strategy_generator(params) - results = backtest(OPTIMIZE_CONFIG['stake_amount'], PROCESSED, stoploss=params['stoploss']) + results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'], + 'processed': PROCESSED, + 'stoploss': params['stoploss']}) result_explanation = format_results(results) total_profit = results.profit_percent.sum() diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 5f899a48a..a9c58e719 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -43,8 +43,10 @@ def test_backtest(default_conf, mocker): exchange._API = Bittrex({'key': '', 'secret': ''}) data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH']) - results = backtest(default_conf['stake_amount'], - optimize.preprocess(data), 10, True) + results = backtest({'stake_amount': default_conf['stake_amount'], + 'processed': optimize.preprocess(data), + 'max_open_trades': 10, + 'realistic': True}) assert not results.empty @@ -54,8 +56,10 @@ def test_backtest_1min_ticker_interval(default_conf, mocker): # Run a backtesting for an exiting 5min ticker_interval data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) - results = backtest(default_conf['stake_amount'], - optimize.preprocess(data), 1, True) + results = backtest({'stake_amount': default_conf['stake_amount'], + 'processed': optimize.preprocess(data), + 'max_open_trades': 1, + 'realistic': True}) assert not results.empty @@ -113,7 +117,10 @@ def simple_backtest(config, contour, num_results): data = load_data_test(contour) processed = optimize.preprocess(data) assert isinstance(processed, dict) - results = backtest(config['stake_amount'], processed, 1, True) + results = backtest({'stake_amount': config['stake_amount'], + 'processed': processed, + 'max_open_trades': 1, + 'realistic': True}) # results :: assert len(results) == num_results @@ -125,8 +132,10 @@ def simple_backtest(config, contour, num_results): def test_backtest2(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH']) - results = backtest(default_conf['stake_amount'], - optimize.preprocess(data), 10, True) + results = backtest({'stake_amount': default_conf['stake_amount'], + 'processed': optimize.preprocess(data), + 'max_open_trades': 10, + 'realistic': True}) assert not results.empty