diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a1cf94774..e507ab2fd 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -189,6 +189,15 @@ class Backtesting: self.rejected_trades = 0 self.dataprovider.clear_cache() + def check_abort(self): + """ + Check if abort was requested, raise DependencyException if that's the case + Only applies to Interactive backtest mode (webserver mode) + """ + if self.abort: + self.abort = False + raise DependencyException("Stop requested") + def _get_ohlcv_as_lists(self, processed: Dict[str, DataFrame]) -> Dict[str, Tuple]: """ Helper function to convert a processed dataframes into lists for performance reasons. @@ -203,8 +212,7 @@ class Backtesting: # Create dict with data for pair, pair_data in processed.items(): - if self.abort: - raise DependencyException("Stop requested") + self.check_abort() self.progress.increment() if not pair_data.empty: pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist @@ -422,8 +430,7 @@ class Backtesting: # Loop timerange and get candle for each pair at that point in time while tmp <= end_date: open_trade_count_start = open_trade_count - if self.abort: - raise DependencyException("Stop requested") + self.check_abort() for i, pair in enumerate(data): row_index = indexes[pair] try: diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index bdd9bc522..cd40438d2 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -52,8 +52,8 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac # TODO: Investigate if enabling protections can be dynamically ingested from here... from freqtrade.optimize.backtesting import Backtesting ApiServer._bt = Backtesting(btconfig) - # Reset data if backtesting is reloaded + # Only reload data if timeframe or timerange changed. if (not ApiServer._backtestdata or not ApiServer._bt_timerange or lastconfig.get('timerange') != btconfig['timerange'] or lastconfig.get('timeframe') != strat.timeframe): diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 30d86f979..5427bbf92 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -346,6 +346,20 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC']) +def test_backtest_abort(default_conf, mocker, testdatadir) -> None: + patch_exchange(mocker) + backtesting = Backtesting(default_conf) + backtesting.check_abort() + + backtesting.abort = True + + with pytest.raises(DependencyException, match="Stop requested"): + backtesting.check_abort() + # abort flag resets + assert backtesting.abort is False + assert backtesting.progress.progress == 0 + + def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: def get_timerange(input1): return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 329d5e32f..c3a958ff5 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1252,10 +1252,6 @@ def test_api_backtesting(botclient, mocker, fee): assert not result['running'] assert result['status_msg'] == 'Backtest reset' - # bt_mock = mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', - # return_value=(1, 2)) - # stats_mock = mocker.patch('freqtrade.optimize.optimize_reports.generate_backtest_stats') - # bt_mock.load_bt_data = MagicMock(return_value=(xxx, 'asdfadf')) # start backtesting data = { "strategy": "DefaultStrategy", @@ -1285,6 +1281,13 @@ def test_api_backtesting(botclient, mocker, fee): assert result['progress'] == 1 assert result['backtest_result'] + rc = client_get(client, f"{BASE_URI}/backtest/abort") + assert_response(rc) + result = rc.json() + assert result['status'] == 'not_running' + assert not result['running'] + assert result['status_msg'] == 'Backtest ended' + # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc)