generate_backtest_stats must take config options from the strategy

config

as a strategy can override certain options.
This commit is contained in:
Matthias 2020-09-25 20:39:00 +02:00
parent 378f03a5b1
commit ff3e2641ae
3 changed files with 85 additions and 72 deletions

View File

@ -380,12 +380,6 @@ class Backtesting:
logger.info('Using stake_currency: %s ...', self.config['stake_currency']) logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
logger.info('Using stake_amount: %s ...', self.config['stake_amount']) 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) position_stacking = self.config.get('position_stacking', False)
data, timerange = self.load_bt_data() data, timerange = self.load_bt_data()
@ -395,6 +389,15 @@ class Backtesting:
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
self._set_strategy(strat) 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 # need to reprocess data every time to populate signals
preprocessed = self.strategy.ohlcvdata_to_dataframe(data) preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
@ -407,7 +410,7 @@ class Backtesting:
f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
f'({(max_date - min_date).days} days)..') f'({(max_date - min_date).days} days)..')
# Execute backtest and print results # Execute backtest and print results
all_results[self.strategy.get_strategy_name()] = self.backtest( results = self.backtest(
processed=preprocessed, processed=preprocessed,
stake_amount=self.config['stake_amount'], stake_amount=self.config['stake_amount'],
start_date=min_date, start_date=min_date,
@ -415,8 +418,12 @@ class Backtesting:
max_open_trades=max_open_trades, max_open_trades=max_open_trades,
position_stacking=position_stacking, 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) min_date=min_date, max_date=max_date)
if self.config.get('export', False): if self.config.get('export', False):
store_backtest_stats(self.config['exportfilename'], stats) store_backtest_stats(self.config['exportfilename'], stats)

View File

@ -1,7 +1,7 @@
import logging import logging
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List from typing import Any, Dict, List, Union
from arrow import Arrow from arrow import Arrow
from pandas import DataFrame from pandas import DataFrame
@ -143,19 +143,18 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
return tabular_data return tabular_data
def generate_strategy_metrics(stake_currency: str, max_open_trades: int, def generate_strategy_metrics(all_results: Dict) -> List[Dict]:
all_results: Dict) -> List[Dict]:
""" """
Generate summary per strategy 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 <Strategyname: BacktestResult> containing results for all strategies :param all_results: Dict of <Strategyname: BacktestResult> containing results for all strategies
:return: List of Dicts containing the metrics per Strategy :return: List of Dicts containing the metrics per Strategy
""" """
tabular_data = [] tabular_data = []
for strategy, results in all_results.items(): 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 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], def generate_backtest_stats(btdata: Dict[str, DataFrame],
all_results: Dict[str, DataFrame], all_results: Dict[str, Dict[str, Union[DataFrame, Dict]]],
min_date: Arrow, max_date: Arrow min_date: Arrow, max_date: Arrow
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
:param config: Configuration object used for backtest
:param btdata: Backtest data :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 min_date: Backtest start date
:param max_date: Backtest end date :param max_date: Backtest end date
:return: :return:
Dictionary containing results per strategy and a stratgy summary. 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': {}} result: Dict[str, Any] = {'strategy': {}}
market_change = calculate_market_change(btdata, 'close') 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, pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
max_open_trades=max_open_trades, max_open_trades=max_open_trades,
@ -310,9 +313,7 @@ def generate_backtest_stats(config: Dict, btdata: Dict[str, DataFrame],
'drawdown_end_ts': 0, 'drawdown_end_ts': 0,
}) })
strategy_results = generate_strategy_metrics(stake_currency=stake_currency, strategy_results = generate_strategy_metrics(all_results=all_results)
max_open_trades=max_open_trades,
all_results=all_results)
result['strategy_comparison'] = strategy_results result['strategy_comparison'] = strategy_results

View File

@ -60,32 +60,35 @@ def test_generate_backtest_stats(default_conf, testdatadir):
default_conf.update({'strategy': 'DefaultStrategy'}) default_conf.update({'strategy': 'DefaultStrategy'})
StrategyResolver.load_strategy(default_conf) StrategyResolver.load_strategy(default_conf)
results = {'DefStrat': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC", results = {'DefStrat': {
"UNITTEST/BTC", "UNITTEST/BTC"], 'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780], "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003], "profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
Arrow(2017, 11, 14, 21, 36, 00).datetime, "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime, Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime], Arrow(2017, 11, 14, 22, 12, 00).datetime,
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, Arrow(2017, 11, 14, 22, 44, 00).datetime],
Arrow(2017, 11, 14, 22, 10, 00).datetime, "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime, Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime], Arrow(2017, 11, 14, 22, 43, 00).datetime,
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214], Arrow(2017, 11, 14, 22, 58, 00).datetime],
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"trade_duration": [123, 34, 31, 14], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"open_at_end": [False, False, False, True], "trade_duration": [123, 34, 31, 14],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS, "open_at_end": [False, False, False, True],
SellType.ROI, SellType.FORCE_SELL] "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
})} SellType.ROI, SellType.FORCE_SELL]
}),
'config': default_conf}
}
timerange = TimeRange.parse_timerange('1510688220-1510700340') timerange = TimeRange.parse_timerange('1510688220-1510700340')
min_date = Arrow.fromtimestamp(1510688220) min_date = Arrow.fromtimestamp(1510688220)
max_date = Arrow.fromtimestamp(1510700340) max_date = Arrow.fromtimestamp(1510700340)
btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, btdata = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True) 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 isinstance(stats, dict)
assert 'strategy' in stats assert 'strategy' in stats
assert 'DefStrat' in stats['strategy'] assert 'DefStrat' in stats['strategy']
@ -93,29 +96,32 @@ def test_generate_backtest_stats(default_conf, testdatadir):
strat_stats = stats['strategy']['DefStrat'] strat_stats = stats['strategy']['DefStrat']
assert strat_stats['backtest_start'] == min_date.datetime assert strat_stats['backtest_start'] == min_date.datetime
assert strat_stats['backtest_end'] == max_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 # Above sample had no loosing trade
assert strat_stats['max_drawdown'] == 0.0 assert strat_stats['max_drawdown'] == 0.0
results = {'DefStrat': pd.DataFrame( results = {'DefStrat': {
{"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"], 'results': pd.DataFrame(
"profit_percent": [0.003312, 0.010801, -0.013803, 0.002780], {"pair": ["UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC", "UNITTEST/BTC"],
"profit_abs": [0.000003, 0.000011, -0.000014, 0.000003], "profit_percent": [0.003312, 0.010801, -0.013803, 0.002780],
"open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime, "profit_abs": [0.000003, 0.000011, -0.000014, 0.000003],
Arrow(2017, 11, 14, 21, 36, 00).datetime, "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
Arrow(2017, 11, 14, 22, 12, 00).datetime, Arrow(2017, 11, 14, 21, 36, 00).datetime,
Arrow(2017, 11, 14, 22, 44, 00).datetime], Arrow(2017, 11, 14, 22, 12, 00).datetime,
"close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime, Arrow(2017, 11, 14, 22, 44, 00).datetime],
Arrow(2017, 11, 14, 22, 10, 00).datetime, "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
Arrow(2017, 11, 14, 22, 43, 00).datetime, Arrow(2017, 11, 14, 22, 10, 00).datetime,
Arrow(2017, 11, 14, 22, 58, 00).datetime], Arrow(2017, 11, 14, 22, 43, 00).datetime,
"open_rate": [0.002543, 0.003003, 0.003089, 0.003214], Arrow(2017, 11, 14, 22, 58, 00).datetime],
"close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
"trade_duration": [123, 34, 31, 14], "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"open_at_end": [False, False, False, True], "trade_duration": [123, 34, 31, 14],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS, "open_at_end": [False, False, False, True],
SellType.ROI, SellType.FORCE_SELL] "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['max_drawdown'] == 0.0
assert strat_stats['drawdown_start'] == Arrow.fromtimestamp(0).datetime 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) 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 = {}
results['TestStrategy1'] = pd.DataFrame( results['TestStrategy1'] = {'results': pd.DataFrame(
{ {
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, 0.3], 'profit_percent': [0.1, 0.2, 0.3],
@ -296,8 +303,8 @@ def test_text_table_strategy(default_conf, mocker):
'losses': [0, 0, 1], 'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
} }
) ), 'config': default_conf}
results['TestStrategy2'] = pd.DataFrame( results['TestStrategy2'] = {'results': pd.DataFrame(
{ {
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'], 'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
'profit_percent': [0.4, 0.2, 0.3], 'profit_percent': [0.4, 0.2, 0.3],
@ -308,7 +315,7 @@ def test_text_table_strategy(default_conf, mocker):
'losses': [0, 0, 1], 'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
} }
) ), 'config': default_conf}
result_str = ( result_str = (
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot' '| 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 |' ' 45.00 | 0:20:00 | 3 | 0 | 0 |'
) )
strategy_results = generate_strategy_metrics(stake_currency='BTC', strategy_results = generate_strategy_metrics(all_results=results)
max_open_trades=2,
all_results=results)
assert text_table_strategy(strategy_results, 'BTC') == result_str assert text_table_strategy(strategy_results, 'BTC') == result_str