Implement cli options for backtesting-analysis date filtering
This commit is contained in:
parent
391817243c
commit
4790aaaae1
@ -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
|
||||||
|
```
|
||||||
|
@ -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).
|
||||||
|
@ -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']
|
|
||||||
)
|
|
||||||
|
@ -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",
|
||||||
|
@ -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.',
|
||||||
|
@ -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',
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user