From 3adda84b96bdcde1909a6cecb12ef9b3fbd9296c Mon Sep 17 00:00:00 2001 From: froggleston Date: Tue, 24 May 2022 20:27:15 +0100 Subject: [PATCH] Update docs, add test --- docs/advanced-backtesting.md | 16 ++-- freqtrade/commands/analyze_commands.py | 2 - freqtrade/data/entryexitanalysis.py | 17 ++-- tests/data/test_entryexitanalysis.py | 94 +++++++++++++++++++++++ tests/strategy/strats/strategy_test_v3.py | 9 ++- 5 files changed, 117 insertions(+), 21 deletions(-) create mode 100755 tests/data/test_entryexitanalysis.py diff --git a/docs/advanced-backtesting.md b/docs/advanced-backtesting.md index 2a484da69..4b40bad8e 100644 --- a/docs/advanced-backtesting.md +++ b/docs/advanced-backtesting.md @@ -22,23 +22,19 @@ DataFrame of the candles that resulted in buy signals. Depending on how many buy makes, this file may get quite large, so periodically check your `user_data/backtest_results` folder to delete old exports. -To analyze the buy tags, we need to use the `buy_reasons.py` script from -[froggleston's repo](https://github.com/froggleston/freqtrade-buyreasons). Follow the instructions -in their README to copy the script into your `freqtrade/scripts/` folder. - Before running your next backtest, make sure you either delete your old backtest results or run backtesting with the `--cache none` option to make sure no cached results are used. If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` file in the `user_data/backtest_results` folder. -Now run the `buy_reasons.py` script, supplying a few options: +To analyze the entry/exit tags, we now need to use the `freqtrade analysis` command: ``` bash -python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 +freqtrade analysis -c -s --analysis_groups 0,1,2,3,4 ``` -The `-g` option is used to specify the various tabular outputs, ranging from the simplest (0) +The `--analysis_groups` option is used to specify the various tabular outputs, ranging from the simplest (0) to the most detailed per pair, per buy and per sell tag (4). More options are available by running with the `-h` option. @@ -54,18 +50,18 @@ To show only certain buy and sell tags in the displayed output, use the followin For example: ```bash -python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --enter_reason_list "enter_tag_a,enter_tag_b" --exit_reason_list "roi,custom_exit_tag_a,stop_loss" +freqtrade analysis -c -s --analysis_groups 0,1,2,3,4 --enter_reason_list "enter_tag_a,enter_tag_b" --exit_reason_list "roi,custom_exit_tag_a,stop_loss" ``` ### Outputting signal candle indicators -The real power of the buy_reasons.py script comes from the ability to print out the indicator +The real power of `freqtrade analysis` comes from the ability to print out the indicator values present on signal candles to allow fine-grained investigation and tuning of buy signal indicators. To print out a column for a given set of indicators, use the `--indicator-list` option: ```bash -python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --enter_reason_list "enter_tag_a,enter_tag_b" --exit_reason_list "roi,custom_exit_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" +freqtrade analysis -c -s --analysis_groups 0,1,2,3,4 --enter_reason_list "enter_tag_a,enter_tag_b" --exit_reason_list "roi,custom_exit_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" ``` The indicators have to be present in your strategy's main DataFrame (either for your main diff --git a/freqtrade/commands/analyze_commands.py b/freqtrade/commands/analyze_commands.py index 73ae19eaf..56330bed3 100755 --- a/freqtrade/commands/analyze_commands.py +++ b/freqtrade/commands/analyze_commands.py @@ -52,8 +52,6 @@ def start_analysis_entries_exits(args: Dict[str, Any]) -> None: # Initialize configuration config = setup_analyze_configuration(args, RunMode.BACKTEST) - print(config) - logger.info('Starting freqtrade in analysis mode') process_entry_exit_reasons(Path(config['user_data_dir'], 'backtest_results'), diff --git a/freqtrade/data/entryexitanalysis.py b/freqtrade/data/entryexitanalysis.py index 8bfc940dc..9d6c470da 100755 --- a/freqtrade/data/entryexitanalysis.py +++ b/freqtrade/data/entryexitanalysis.py @@ -15,10 +15,18 @@ logger = logging.getLogger(__name__) def _load_signal_candles(backtest_dir: Path): - scpf = Path(backtest_dir, - os.path.splitext( - get_latest_backtest_filename(backtest_dir))[0] + "_signals.pkl" - ) + + if backtest_dir.is_dir(): + scpf = Path(backtest_dir, + os.path.splitext( + get_latest_backtest_filename(backtest_dir))[0] + "_signals.pkl" + ) + else: + scpf = Path(os.path.splitext( + get_latest_backtest_filename(backtest_dir))[0] + "_signals.pkl" + ) + + print(scpf) try: scp = open(scpf, "rb") signal_candles = joblib.load(scp) @@ -246,7 +254,6 @@ def process_entry_exit_reasons(backtest_dir: Path, signal_candles = _load_signal_candles(backtest_dir) analysed_trades_dict = _process_candles_and_indicators(pairlist, strategy_name, trades, signal_candles) - _print_results(analysed_trades_dict, strategy_name, analysis_groups, diff --git a/tests/data/test_entryexitanalysis.py b/tests/data/test_entryexitanalysis.py new file mode 100755 index 000000000..548cd88b9 --- /dev/null +++ b/tests/data/test_entryexitanalysis.py @@ -0,0 +1,94 @@ +from pathlib import Path +from unittest.mock import MagicMock, PropertyMock + +import pandas as pd + +from freqtrade.commands.analyze_commands import start_analysis_entries_exits +from freqtrade.commands.optimize_commands import start_backtesting +from freqtrade.enums import ExitType +from tests.conftest import get_args, patch_exchange, patched_configuration_load_config_file + + +def test_backtest_analysis_nomock(default_conf, mocker, caplog, testdatadir, capsys): + default_conf.update({ + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, + 'analysis_groups': "0", + 'enter_reason_list': "all", + 'exit_reason_list': "all", + 'indicator_list': "bb_upperband,ema_10" + }) + patch_exchange(mocker) + result1 = pd.DataFrame({'pair': ['ETH/BTC', 'LTC/BTC'], + 'profit_ratio': [0.0, 0.0], + 'profit_abs': [0.0, 0.0], + 'open_date': pd.to_datetime(['2018-01-29 18:40:00', + '2018-01-30 03:30:00', ], utc=True + ), + 'close_date': pd.to_datetime(['2018-01-29 20:45:00', + '2018-01-30 05:35:00', ], utc=True), + 'trade_duration': [235, 40], + 'is_open': [False, False], + 'stake_amount': [0.01, 0.01], + 'open_rate': [0.104445, 0.10302485], + 'close_rate': [0.104969, 0.103541], + "is_short": [False, False], + 'enter_tag': ["enter_tag_long", "enter_tag_long"], + 'exit_reason': [ExitType.ROI, ExitType.ROI] + }) + + backtestmock = MagicMock(side_effect=[ + { + 'results': result1, + 'config': default_conf, + 'locks': [], + 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, + 'canceled_trade_entries': 0, + 'canceled_entry_orders': 0, + 'replaced_entry_orders': 0, + 'final_balance': 1000, + } + ]) + mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', + PropertyMock(return_value=['ETH/BTC', 'LTC/BTC', 'DASH/BTC'])) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + + patched_configuration_load_config_file(mocker, default_conf) + + args = [ + 'backtesting', + '--config', 'config.json', + '--datadir', str(testdatadir), + '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), + '--timeframe', '5m', + '--timerange', '1515560100-1517287800', + '--export', 'signals', + '--cache', 'none', + '--strategy-list', + 'StrategyTestV3', + ] + args = get_args(args) + start_backtesting(args) + + captured = capsys.readouterr() + assert 'BACKTESTING REPORT' in captured.out + assert 'EXIT REASON STATS' in captured.out + assert 'LEFT OPEN TRADES REPORT' in captured.out + + args = [ + 'analysis', + '--config', 'config.json', + '--datadir', str(testdatadir), + '--analysis_groups', '0', + '--strategy', + 'StrategyTestV3', + ] + args = get_args(args) + start_analysis_entries_exits(args) + + captured = capsys.readouterr() + assert 'enter_tag_long' in captured.out diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index df83d3663..f1c9d8e99 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -143,12 +143,13 @@ class StrategyTestV3(IStrategy): (dataframe['adx'] > 65) & (dataframe['plus_di'] > self.buy_plusdi.value) ), - 'enter_long'] = 1 + ['enter_long', 'enter_tag']] = 1, 'enter_tag_long' + dataframe.loc[ ( qtpylib.crossed_below(dataframe['rsi'], self.sell_rsi.value) ), - 'enter_short'] = 1 + ['enter_short', 'enter_tag']] = 1, 'enter_tag_short' return dataframe @@ -166,13 +167,13 @@ class StrategyTestV3(IStrategy): (dataframe['adx'] > 70) & (dataframe['minus_di'] > self.sell_minusdi.value) ), - 'exit_long'] = 1 + ['exit_long', 'exit_tag']] = 1, 'exit_tag_long' dataframe.loc[ ( qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value) ), - 'exit_short'] = 1 + ['exit_long', 'exit_tag']] = 1, 'exit_tag_short' return dataframe