diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 005ec9fb8..bfdb67ec2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -380,12 +380,6 @@ class Backtesting: logger.info('Using stake_currency: %s ...', self.config['stake_currency']) logger.info('Using stake_amount: %s ...', self.config['stake_amount']) - # Use max_open_trades in backtesting, except --disable-max-market-positions is set - if self.config.get('use_max_market_positions', True): - max_open_trades = self.config['max_open_trades'] - else: - logger.info('Ignoring max_open_trades (--disable-max-market-positions was used) ...') - max_open_trades = 0 position_stacking = self.config.get('position_stacking', False) data, timerange = self.load_bt_data() @@ -395,6 +389,15 @@ class Backtesting: logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) self._set_strategy(strat) + # Use max_open_trades in backtesting, except --disable-max-market-positions is set + if self.config.get('use_max_market_positions', True): + # Must come from strategy config, as the strategy may modify this setting. + max_open_trades = self.strategy.config['max_open_trades'] + else: + logger.info( + 'Ignoring max_open_trades (--disable-max-market-positions was used) ...') + max_open_trades = 0 + # need to reprocess data every time to populate signals preprocessed = self.strategy.ohlcvdata_to_dataframe(data) @@ -407,7 +410,7 @@ class Backtesting: f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(max_date - min_date).days} days)..') # Execute backtest and print results - all_results[self.strategy.get_strategy_name()] = self.backtest( + results = self.backtest( processed=preprocessed, stake_amount=self.config['stake_amount'], start_date=min_date, @@ -415,8 +418,12 @@ class Backtesting: max_open_trades=max_open_trades, position_stacking=position_stacking, ) + all_results[self.strategy.get_strategy_name()] = { + 'results': results, + 'config': self.strategy.config, + } - stats = generate_backtest_stats(self.config, data, all_results, + stats = generate_backtest_stats(data, all_results, min_date=min_date, max_date=max_date) if self.config.get('export', False): store_backtest_stats(self.config['exportfilename'], stats) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 18ca7a3a7..696e63b25 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Dict, List +from typing import Any, Dict, List, Union from arrow import Arrow from pandas import DataFrame @@ -143,19 +143,18 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List return tabular_data -def generate_strategy_metrics(stake_currency: str, max_open_trades: int, - all_results: Dict) -> List[Dict]: +def generate_strategy_metrics(all_results: Dict) -> List[Dict]: """ Generate summary per strategy - :param stake_currency: stake-currency - used to correctly name headers - :param max_open_trades: Maximum allowed open trades used for backtest :param all_results: Dict of containing results for all strategies :return: List of Dicts containing the metrics per Strategy """ tabular_data = [] for strategy, results in all_results.items(): - tabular_data.append(_generate_result_line(results, max_open_trades, strategy)) + tabular_data.append(_generate_result_line( + results['results'], results['config']['max_open_trades'], strategy) + ) return tabular_data @@ -219,25 +218,29 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]: } -def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], - all_results: Dict[str, DataFrame], +def generate_backtest_stats(btdata: Dict[str, DataFrame], + all_results: Dict[str, Dict[str, Union[DataFrame, Dict]]], min_date: Arrow, max_date: Arrow ) -> Dict[str, Any]: """ - :param config: Configuration object used for backtest :param btdata: Backtest data - :param all_results: backtest result - dictionary with { Strategy: results}. + :param all_results: backtest result - dictionary in the form: + { Strategy: {'results: results, 'config: config}}. :param min_date: Backtest start date :param max_date: Backtest end date :return: Dictionary containing results per strategy and a stratgy summary. """ - stake_currency = config['stake_currency'] - max_open_trades = config['max_open_trades'] result: Dict[str, Any] = {'strategy': {}} market_change = calculate_market_change(btdata, 'close') - for strategy, results in all_results.items(): + for strategy, content in all_results.items(): + results: Dict[str, DataFrame] = content['results'] + if not isinstance(results, DataFrame): + continue + config = content['config'] + max_open_trades = config['max_open_trades'] + stake_currency = config['stake_currency'] pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, max_open_trades=max_open_trades, @@ -310,9 +313,7 @@ def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame], 'drawdown_end_ts': 0, }) - strategy_results = generate_strategy_metrics(stake_currency=stake_currency, - max_open_trades=max_open_trades, - all_results=all_results) + strategy_results = generate_strategy_metrics(all_results=all_results) result['strategy_comparison'] = strategy_results diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index d61fd2bc5..fe030e315 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -60,32 +60,35 @@ def test_generate_backtest_stats(default_conf, testdatadir): default_conf.update({'strategy': 'DefaultStrategy'}) StrategyResolver.load_strategy(default_conf) - results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", - "UNITTEST/BTC", "UNITTEST/BTC"], - "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], - "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], - "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, - Arrow(2017, 11, 14, 21, 36, 00).datetime, - Arrow(2017, 11, 14, 22, 12, 00).datetime, - Arrow(2017, 11, 14, 22, 44, 00).datetime], - "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, - Arrow(2017, 11, 14, 22, 10, 00).datetime, - Arrow(2017, 11, 14, 22, 43, 00).datetime, - Arrow(2017, 11, 14, 22, 58, 00).datetime], - "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], - "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], - "trade_duration": [123, 34, 31, 14], - "open_at_end": [False, False, False, True], - "sell_reason": [SellType.ROI, SellType.STOP_LOSS, - SellType.ROI, SellType.FORCE_SELL] - })} + results = {'DefStrat': { + 'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", + "UNITTEST/BTC", "UNITTEST/BTC"], + "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], + "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], + "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, + Arrow(2017, 11, 14, 21, 36, 00).datetime, + Arrow(2017, 11, 14, 22, 12, 00).datetime, + Arrow(2017, 11, 14, 22, 44, 00).datetime], + "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, + Arrow(2017, 11, 14, 22, 10, 00).datetime, + Arrow(2017, 11, 14, 22, 43, 00).datetime, + Arrow(2017, 11, 14, 22, 58, 00).datetime], + "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], + "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], + "trade_duration": [123, 34, 31, 14], + "open_at_end": [False, False, False, True], + "sell_reason": [SellType.ROI, SellType.STOP_LOSS, + SellType.ROI, SellType.FORCE_SELL] + }), + 'config': default_conf} + } timerange = TimeRange.parse_timerange('1510688220-1510700340') min_date = Arrow.fromtimestamp(1510688220) max_date = Arrow.fromtimestamp(1510700340) btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, fill_up_missing=True) - stats = generate_backtest_stats(default_conf, btdata, results, min_date, max_date) + stats = generate_backtest_stats(btdata, results, min_date, max_date) assert isinstance(stats, dict) assert 'strategy' in stats assert 'DefStrat' in stats['strategy'] @@ -93,29 +96,32 @@ def test_generate_backtest_stats(default_conf, testdatadir): strat_stats = stats['strategy']['DefStrat'] assert strat_stats['backtest_start'] == min_date.datetime assert strat_stats['backtest_end'] == max_date.datetime - assert strat_stats['total_trades'] == len(results['DefStrat']) + assert strat_stats['total_trades'] == len(results['DefStrat']['results']) # Above sample had no loosing trade assert strat_stats['max_drawdown'] == 0.0 - results = {'DefStrat': pd.DataFrame( - {"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"], - "profit_percent": [0.003312, 0.010801, -0.013803, 0.002780], - "profit_abs": [0.000003, 0.000011, -0.000014, 0.000003], - "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, - Arrow(2017, 11, 14, 21, 36, 00).datetime, - Arrow(2017, 11, 14, 22, 12, 00).datetime, - Arrow(2017, 11, 14, 22, 44, 00).datetime], - "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, - Arrow(2017, 11, 14, 22, 10, 00).datetime, - Arrow(2017, 11, 14, 22, 43, 00).datetime, - Arrow(2017, 11, 14, 22, 58, 00).datetime], - "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], - "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], - "trade_duration": [123, 34, 31, 14], - "open_at_end": [False, False, False, True], - "sell_reason": [SellType.ROI, SellType.STOP_LOSS, - SellType.ROI, SellType.FORCE_SELL] - })} + results = {'DefStrat': { + 'results': pd.DataFrame( + {"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"], + "profit_percent": [0.003312, 0.010801, -0.013803, 0.002780], + "profit_abs": [0.000003, 0.000011, -0.000014, 0.000003], + "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, + Arrow(2017, 11, 14, 21, 36, 00).datetime, + Arrow(2017, 11, 14, 22, 12, 00).datetime, + Arrow(2017, 11, 14, 22, 44, 00).datetime], + "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, + Arrow(2017, 11, 14, 22, 10, 00).datetime, + Arrow(2017, 11, 14, 22, 43, 00).datetime, + Arrow(2017, 11, 14, 22, 58, 00).datetime], + "open_rate": [0.002543, 0.003003, 0.003089, 0.003214], + "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], + "trade_duration": [123, 34, 31, 14], + "open_at_end": [False, False, False, True], + "sell_reason": [SellType.ROI, SellType.STOP_LOSS, + SellType.ROI, SellType.FORCE_SELL] + }), + 'config': default_conf} + } assert strat_stats['max_drawdown'] == 0.0 assert strat_stats['drawdown_start'] == Arrow.fromtimestamp(0).datetime @@ -283,9 +289,10 @@ def test_generate_sell_reason_stats(default_conf): assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2) -def test_text_table_strategy(default_conf, mocker): +def test_text_table_strategy(default_conf): + default_conf['max_open_trades'] = 2 results = {} - results['TestStrategy1'] = pd.DataFrame( + results['TestStrategy1'] = {'results': pd.DataFrame( { 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], 'profit_percent': [0.1, 0.2, 0.3], @@ -296,8 +303,8 @@ def test_text_table_strategy(default_conf, mocker): 'losses': [0, 0, 1], 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] } - ) - results['TestStrategy2'] = pd.DataFrame( + ), 'config': default_conf} + results['TestStrategy2'] = {'results': pd.DataFrame( { 'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'], 'profit_percent': [0.4, 0.2, 0.3], @@ -308,7 +315,7 @@ def test_text_table_strategy(default_conf, mocker): 'losses': [0, 0, 1], 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] } - ) + ), 'config': default_conf} result_str = ( '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot' @@ -321,9 +328,7 @@ def test_text_table_strategy(default_conf, mocker): ' 45.00 | 0:20:00 | 3 | 0 | 0 |' ) - strategy_results = generate_strategy_metrics(stake_currency='BTC', - max_open_trades=2, - all_results=results) + strategy_results = generate_strategy_metrics(all_results=results) assert text_table_strategy(strategy_results, 'BTC') == result_str