Implement cli options for backtesting-analysis date filtering

This commit is contained in:
froggleston 2022-11-26 16:58:56 +00:00
parent 391817243c
commit 4790aaaae1
8 changed files with 107 additions and 47 deletions

View File

@ -100,3 +100,18 @@ freqtrade backtesting-analysis -c <config.json> --analysis-groups 0 2 --enter-re
The indicators have to be present in your strategy's main DataFrame (either for your main The indicators have to be present in your strategy's main DataFrame (either for your main
timeframe or for informative timeframes) otherwise they will simply be ignored in the script timeframe or for informative timeframes) otherwise they will simply be ignored in the script
output. output.
### Filtering the trade output by date
To show only trades between dates within your backtested timerange, supply the following option(s) in YYYYMMDD format:
```
--analysis-date-start : Start date to filter output trades, inclusive. e.g. 20220101
--analysis-date-end : End date to filter output trades, exclusive. e.g. 20220131
```
For example, if your backtest timerange was `20220101-20221231` but you only want to output trades in January:
```bash
freqtrade backtesting-analysis -c <config.json> --analysis-date-start 20220101 --analysis-date-end 20220201
```

View File

@ -722,6 +722,8 @@ usage: freqtrade backtesting-analysis [-h] [-v] [--logfile FILE] [-V]
[--enter-reason-list ENTER_REASON_LIST [ENTER_REASON_LIST ...]] [--enter-reason-list ENTER_REASON_LIST [ENTER_REASON_LIST ...]]
[--exit-reason-list EXIT_REASON_LIST [EXIT_REASON_LIST ...]] [--exit-reason-list EXIT_REASON_LIST [EXIT_REASON_LIST ...]]
[--indicator-list INDICATOR_LIST [INDICATOR_LIST ...]] [--indicator-list INDICATOR_LIST [INDICATOR_LIST ...]]
[--analysis-date-start YYYYMMDD]
[--analysis-date-end YYYYMMDD]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -744,6 +746,12 @@ optional arguments:
--indicator-list INDICATOR_LIST [INDICATOR_LIST ...] --indicator-list INDICATOR_LIST [INDICATOR_LIST ...]
Comma separated list of indicators to analyse. e.g. Comma separated list of indicators to analyse. e.g.
'close,rsi,bb_lowerband,profit_abs' 'close,rsi,bb_lowerband,profit_abs'
--analysis-date-start YYYYMMDD
Start date to filter trades for analysis (inclusive). e.g.
20220101
--analysis-date-end YYYYMMDD
End date to filter trades for analysis (exclusive). e.g.
20220131
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).

View File

@ -60,10 +60,4 @@ def start_analysis_entries_exits(args: Dict[str, Any]) -> None:
logger.info('Starting freqtrade in analysis mode') logger.info('Starting freqtrade in analysis mode')
process_entry_exit_reasons(config['exportfilename'], process_entry_exit_reasons(config)
config['exchange']['pair_whitelist'],
config['analysis_groups'],
config['enter_reason_list'],
config['exit_reason_list'],
config['indicator_list']
)

View File

@ -106,7 +106,8 @@ ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperop
"disableparamexport", "backtest_breakdown"] "disableparamexport", "backtest_breakdown"]
ARGS_ANALYZE_ENTRIES_EXITS = ["exportfilename", "analysis_groups", "enter_reason_list", ARGS_ANALYZE_ENTRIES_EXITS = ["exportfilename", "analysis_groups", "enter_reason_list",
"exit_reason_list", "indicator_list"] "exit_reason_list", "indicator_list",
"analysis_date_start", "analysis_date_end"]
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
"list-markets", "list-pairs", "list-strategies", "list-freqaimodels", "list-markets", "list-pairs", "list-strategies", "list-freqaimodels",

View File

@ -658,6 +658,16 @@ AVAILABLE_CLI_OPTIONS = {
nargs='+', nargs='+',
default=[], default=[],
), ),
"analysis_date_start": Arg(
"--analysis-date-start",
help=("Start date to filter trades for analysis (inclusive). "
"e.g. '20220101'"),
),
"analysis_date_end": Arg(
"--analysis-date-end",
help=("End date to filter trades for analysis (exclusive). "
"e.g. '20220131'"),
),
"freqaimodel": Arg( "freqaimodel": Arg(
'--freqaimodel', '--freqaimodel',
help='Specify a custom freqaimodels.', help='Specify a custom freqaimodels.',

View File

@ -462,6 +462,12 @@ class Configuration:
self._args_to_config(config, argname='indicator_list', self._args_to_config(config, argname='indicator_list',
logstring='Analysis indicator list: {}') logstring='Analysis indicator list: {}')
self._args_to_config(config, argname='analysis_date_start',
logstring='Analysis filter start date: {}')
self._args_to_config(config, argname='analysis_date_end',
logstring='Analysis filter end date: {}')
def _process_runmode(self, config: Config) -> None: def _process_runmode(self, config: Config) -> None:
self._args_to_config(config, argname='dry_run', self._args_to_config(config, argname='dry_run',

View File

@ -1,11 +1,12 @@
import logging import logging
from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import List, Optional
import joblib import joblib
import pandas as pd import pandas as pd
from tabulate import tabulate from tabulate import tabulate
from freqtrade.constants import Config
from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data, from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data,
load_backtest_stats) load_backtest_stats)
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
@ -153,55 +154,64 @@ def _do_group_table_output(bigdf, glist):
def _select_rows_within_dates(df, date_start=None, date_end=None): def _select_rows_within_dates(df, date_start=None, date_end=None):
dtfmt = "%Y%m%d"
try:
bool(datetime.strptime(date_start, dtfmt))
bool(datetime.strptime(date_end, dtfmt))
except ValueError:
logger.error("Invalid start and/or end date provided. Use YYYYMMDD.")
return None
except TypeError:
return df
if (date_start is not None): if (date_start is not None):
df = df.loc[(df['date'] >= date_start)] df = df.loc[(df['date'] >= date_start)]
if (date_end is not None): if (date_end is not None):
df = df.loc[(df['date'] < date_end)] df = df.loc[(df['date'] < date_end)]
return df return df
def _select_rows_by_entry_exit_tags(df, enter_reason_list, exit_reason_list): def _select_rows_by_tags(df, enter_reason_list, exit_reason_list):
if enter_reason_list and "all" not in enter_reason_list: if enter_reason_list and "all" not in enter_reason_list:
df = df.loc[(df['enter_reason'].isin(enter_reason_list))] df = df.loc[(df['enter_reason'].isin(enter_reason_list))]
if exit_reason_list and "all" not in exit_reason_list: if exit_reason_list and "all" not in exit_reason_list:
df = df.loc[(df['exit_reason'].isin(exit_reason_list))] df = df.loc[(df['exit_reason'].isin(exit_reason_list))]
return df return df
def _print_results(analysed_trades, stratname, analysis_groups, def prepare_results(analysed_trades, stratname,
enter_reason_list, exit_reason_list, enter_reason_list, exit_reason_list,
indicator_list, columns=None,
date_start=None, date_end=None): date_start=None, date_end=None):
if columns is None: res_df = pd.DataFrame()
columns = ['pair', 'open_date', 'close_date', 'profit_abs', 'enter_reason', 'exit_reason']
bigdf = pd.DataFrame()
for pair, trades in analysed_trades[stratname].items(): for pair, trades in analysed_trades[stratname].items():
bigdf = pd.concat([bigdf, trades], ignore_index=True) res_df = pd.concat([res_df, trades], ignore_index=True)
bigdf = _select_rows_within_dates(bigdf, date_start, date_end) res_df = _select_rows_within_dates(res_df, date_start, date_end)
if bigdf.shape[0] > 0 and ('enter_reason' in bigdf.columns): if res_df is not None and res_df.shape[0] > 0 and ('enter_reason' in res_df.columns):
res_df = _select_rows_by_tags(res_df, enter_reason_list, exit_reason_list)
return res_df
def print_results(res_df, analysis_groups, indicator_list):
if res_df.shape[0] > 0:
if analysis_groups: if analysis_groups:
_do_group_table_output(bigdf, analysis_groups) _do_group_table_output(res_df, analysis_groups)
bigdf = _select_rows_by_entry_exit_tags(bigdf, enter_reason_list, exit_reason_list)
if "all" in indicator_list: if "all" in indicator_list:
print(bigdf) print(res_df)
elif indicator_list is not None: elif indicator_list is not None:
available_inds = [] available_inds = []
for ind in indicator_list: for ind in indicator_list:
if ind in bigdf: if ind in res_df:
available_inds.append(ind) available_inds.append(ind)
ilist = ["pair", "enter_reason", "exit_reason"] + available_inds ilist = ["pair", "enter_reason", "exit_reason"] + available_inds
_print_table(bigdf[ilist], sortcols=['exit_reason'], show_index=False) _print_table(res_df[ilist], sortcols=['exit_reason'], show_index=False)
else: else:
print("\\_ No trades to show") print("\\No trades to show")
def _print_table(df, sortcols=None, show_index=False): def _print_table(df, sortcols=None, show_index=False):
@ -220,26 +230,33 @@ def _print_table(df, sortcols=None, show_index=False):
) )
def process_entry_exit_reasons(backtest_dir: Path, def process_entry_exit_reasons(config: Config):
pairlist: List[str],
analysis_groups: Optional[List[str]] = ["0", "1", "2"],
enter_reason_list: Optional[List[str]] = ["all"],
exit_reason_list: Optional[List[str]] = ["all"],
indicator_list: Optional[List[str]] = []):
try: try:
backtest_stats = load_backtest_stats(backtest_dir) analysis_groups = config.get('analysis_groups', [])
enter_reason_list = config.get('enter_reason_list', ["all"])
exit_reason_list = config.get('exit_reason_list', ["all"])
indicator_list = config.get('indicator_list', [])
analysis_date_start = config.get('analysis_date_start', None)
analysis_date_end = config.get('analysis_date_end', None)
backtest_stats = load_backtest_stats(config['exportfilename'])
for strategy_name, results in backtest_stats['strategy'].items(): for strategy_name, results in backtest_stats['strategy'].items():
trades = load_backtest_data(backtest_dir, strategy_name) trades = load_backtest_data(config['exportfilename'], strategy_name)
if not trades.empty: if not trades.empty:
signal_candles = _load_signal_candles(backtest_dir) signal_candles = _load_signal_candles(config['exportfilename'])
analysed_trades_dict = _process_candles_and_indicators(pairlist, strategy_name, analysed_trades_dict = _process_candles_and_indicators(
config['exchange']['pair_whitelist'], strategy_name,
trades, signal_candles) trades, signal_candles)
_print_results(analysed_trades_dict,
strategy_name, res_df = prepare_results(analysed_trades_dict, strategy_name,
enter_reason_list, exit_reason_list,
date_start=analysis_date_start,
date_end=analysis_date_end)
print_results(res_df,
analysis_groups, analysis_groups,
enter_reason_list,
exit_reason_list,
indicator_list) indicator_list)
except ValueError as e: except ValueError as e:

View File

@ -189,3 +189,12 @@ def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, tmp
assert '0.5' in captured.out assert '0.5' in captured.out
assert '1' in captured.out assert '1' in captured.out
assert '2.5' in captured.out assert '2.5' in captured.out
# test date filtering
args = get_args(base_args +
['--analysis-date-start', "20180129",
'--analysis-date-end', "20180130"]
)
start_analysis_entries_exits(args)
captured = capsys.readouterr()
assert 'enter_tag_long_b' not in captured.out