diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index b168affc3..4055845e1 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -16,7 +16,7 @@ from freqtrade.misc import deep_merge_dicts from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, BacktestResponse) from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode -from freqtrade.rpc.api_server.webserver import ApiServer +from freqtrade.rpc.api_server.webserver_bgwork import ApiBG from freqtrade.rpc.rpc import RPCException @@ -30,9 +30,9 @@ router = APIRouter() async def api_start_backtest( # noqa: C901 bt_settings: BacktestRequest, background_tasks: BackgroundTasks, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): - ApiServer._bt['bt_error'] = None + ApiBG._bt['bt_error'] = None """Start backtesting if not done so already""" - if ApiServer._bgtask_running: + if ApiBG._bgtask_running: raise RPCException('Bot Background task already running') if ':' in bt_settings.strategy: @@ -63,30 +63,30 @@ async def api_start_backtest( # noqa: C901 asyncio.set_event_loop(asyncio.new_event_loop()) try: # Reload strategy - lastconfig = ApiServer._bt['last_config'] + lastconfig = ApiBG._bt['last_config'] strat = StrategyResolver.load_strategy(btconfig) validate_config_consistency(btconfig) if ( - not ApiServer._bt['bt'] + not ApiBG._bt['bt'] or lastconfig.get('timeframe') != strat.timeframe or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail') or lastconfig.get('timerange') != btconfig['timerange'] ): from freqtrade.optimize.backtesting import Backtesting - ApiServer._bt['bt'] = Backtesting(btconfig) - ApiServer._bt['bt'].load_bt_data_detail() + ApiBG._bt['bt'] = Backtesting(btconfig) + ApiBG._bt['bt'].load_bt_data_detail() else: - ApiServer._bt['bt'].config = btconfig - ApiServer._bt['bt'].init_backtest() + ApiBG._bt['bt'].config = btconfig + ApiBG._bt['bt'].init_backtest() # Only reload data if timeframe changed. if ( - not ApiServer._bt['data'] - or not ApiServer._bt['timerange'] + not ApiBG._bt['data'] + or not ApiBG._bt['timerange'] or lastconfig.get('timeframe') != strat.timeframe or lastconfig.get('timerange') != btconfig['timerange'] ): - ApiServer._bt['data'], ApiServer._bt['timerange'] = ApiServer._bt[ + ApiBG._bt['data'], ApiBG._bt['timerange'] = ApiBG._bt[ 'bt'].load_bt_data() lastconfig['timerange'] = btconfig['timerange'] @@ -95,27 +95,27 @@ async def api_start_backtest( # noqa: C901 lastconfig['enable_protections'] = btconfig.get('enable_protections') lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet') - ApiServer._bt['bt'].enable_protections = btconfig.get('enable_protections', False) - ApiServer._bt['bt'].strategylist = [strat] - ApiServer._bt['bt'].results = {} - ApiServer._bt['bt'].load_prior_backtest() + ApiBG._bt['bt'].enable_protections = btconfig.get('enable_protections', False) + ApiBG._bt['bt'].strategylist = [strat] + ApiBG._bt['bt'].results = {} + ApiBG._bt['bt'].load_prior_backtest() - ApiServer._bt['bt'].abort = False - if (ApiServer._bt['bt'].results and - strat.get_strategy_name() in ApiServer._bt['bt'].results['strategy']): + ApiBG._bt['bt'].abort = False + if (ApiBG._bt['bt'].results and + strat.get_strategy_name() in ApiBG._bt['bt'].results['strategy']): # When previous result hash matches - reuse that result and skip backtesting. logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}') else: - min_date, max_date = ApiServer._bt['bt'].backtest_one_strategy( - strat, ApiServer._bt['data'], ApiServer._bt['timerange']) + min_date, max_date = ApiBG._bt['bt'].backtest_one_strategy( + strat, ApiBG._bt['data'], ApiBG._bt['timerange']) - ApiServer._bt['bt'].results = generate_backtest_stats( - ApiServer._bt['data'], ApiServer._bt['bt'].all_results, + ApiBG._bt['bt'].results = generate_backtest_stats( + ApiBG._bt['data'], ApiBG._bt['bt'].all_results, min_date=min_date, max_date=max_date) if btconfig.get('export', 'none') == 'trades': store_backtest_stats( - btconfig['exportfilename'], ApiServer._bt['bt'].results, + btconfig['exportfilename'], ApiBG._bt['bt'].results, datetime.now().strftime("%Y-%m-%d_%H-%M-%S") ) @@ -123,13 +123,13 @@ async def api_start_backtest( # noqa: C901 except (Exception, OperationalException, DependencyException) as e: logger.exception(f"Backtesting caused an error: {e}") - ApiServer._bt['bt_error'] = str(e) + ApiBG._bt['bt_error'] = str(e) pass finally: - ApiServer._bgtask_running = False + ApiBG._bgtask_running = False background_tasks.add_task(run_backtest) - ApiServer._bgtask_running = True + ApiBG._bgtask_running = True return { "status": "running", @@ -147,18 +147,18 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): Returns Result after backtesting has been ran. """ from freqtrade.persistence import LocalTrade - if ApiServer._bgtask_running: + if ApiBG._bgtask_running: return { "status": "running", "running": True, - "step": (ApiServer._bt['bt'].progress.action if ApiServer._bt['bt'] + "step": (ApiBG._bt['bt'].progress.action if ApiBG._bt['bt'] else str(BacktestState.STARTUP)), - "progress": ApiServer._bt['bt'].progress.progress if ApiServer._bt['bt'] else 0, + "progress": ApiBG._bt['bt'].progress.progress if ApiBG._bt['bt'] else 0, "trade_count": len(LocalTrade.trades), "status_msg": "Backtest running", } - if not ApiServer._bt['bt']: + if not ApiBG._bt['bt']: return { "status": "not_started", "running": False, @@ -166,13 +166,13 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest not yet executed" } - if ApiServer._bt['bt_error']: + if ApiBG._bt['bt_error']: return { "status": "error", "running": False, "step": "", "progress": 0, - "status_msg": f"Backtest failed with {ApiServer._bt['bt_error']}" + "status_msg": f"Backtest failed with {ApiBG._bt['bt_error']}" } return { @@ -181,14 +181,14 @@ def api_get_backtest(ws_mode=Depends(is_webserver_mode)): "status_msg": "Backtest ended", "step": "finished", "progress": 1, - "backtest_result": ApiServer._bt['bt'].results, + "backtest_result": ApiBG._bt['bt'].results, } @router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): """Reset backtesting""" - if ApiServer._bgtask_running: + if ApiBG._bgtask_running: return { "status": "running", "running": True, @@ -196,12 +196,12 @@ def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest running", } - if ApiServer._bt['bt']: - ApiServer._bt['bt'].cleanup() - del ApiServer._bt['bt'] - ApiServer._bt['bt'] = None - del ApiServer._bt['data'] - ApiServer._bt['data'] = None + if ApiBG._bt['bt']: + ApiBG._bt['bt'].cleanup() + del ApiBG._bt['bt'] + ApiBG._bt['bt'] = None + del ApiBG._bt['data'] + ApiBG._bt['data'] = None logger.info("Backtesting reset") return { "status": "reset", @@ -214,7 +214,7 @@ def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): @router.get('/backtest/abort', response_model=BacktestResponse, tags=['webserver', 'backtest']) def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): - if not ApiServer._bgtask_running: + if not ApiBG._bgtask_running: return { "status": "not_running", "running": False, @@ -222,7 +222,7 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest ended", } - ApiServer._bt['bt'].abort = True + ApiBG._bt['bt'].abort = True return { "status": "stopping", "running": False, diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index bfc1e698c..b46ac88cd 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -6,6 +6,7 @@ from fastapi import Depends from freqtrade.enums import RunMode from freqtrade.persistence import Trade from freqtrade.persistence.models import _request_id_ctx_var +from freqtrade.rpc.api_server.webserver_bgwork import ApiBG from freqtrade.rpc.rpc import RPC, RPCException from .webserver import ApiServer @@ -43,11 +44,11 @@ def get_api_config() -> Dict[str, Any]: def get_exchange(config=Depends(get_config)): - if not ApiServer._exchange: + if not ApiBG._exchange: from freqtrade.resolvers import ExchangeResolver - ApiServer._exchange = ExchangeResolver.load_exchange( + ApiBG._exchange = ExchangeResolver.load_exchange( config, load_leverage_tiers=False) - return ApiServer._exchange + return ApiBG._exchange def get_message_stream(): diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 8030e303b..165849a7f 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -1,6 +1,6 @@ import logging from ipaddress import IPv4Address -from typing import Any, Dict, Optional +from typing import Any, Optional import orjson import uvicorn @@ -36,19 +36,8 @@ class ApiServer(RPCHandler): __initialized = False _rpc: RPC - # Backtesting type: Backtesting - _bt: Dict[str, Any] = { - 'bt': None, - 'data': None, - 'timerange': None, - 'last_config': {}, - 'bt_error': None, - } _has_rpc: bool = False - _bgtask_running: bool = False _config: Config = {} - # Exchange - only available in webserver mode. - _exchange = None # websocket message stuff _message_stream: Optional[MessageStream] = None @@ -85,7 +74,7 @@ class ApiServer(RPCHandler): """ Attach rpc handler """ - if not self._has_rpc: + if not ApiServer._has_rpc: ApiServer._rpc = rpc ApiServer._has_rpc = True else: diff --git a/freqtrade/rpc/api_server/webserver_bgwork.py b/freqtrade/rpc/api_server/webserver_bgwork.py new file mode 100644 index 000000000..cdb1bd031 --- /dev/null +++ b/freqtrade/rpc/api_server/webserver_bgwork.py @@ -0,0 +1,16 @@ + +from typing import Any, Dict + + +class ApiBG(): + # Backtesting type: Backtesting + _bt: Dict[str, Any] = { + 'bt': None, + 'data': None, + 'timerange': None, + 'last_config': {}, + 'bt_error': None, + } + _bgtask_running: bool = False + # Exchange - only available in webserver mode. + _exchange = None diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 1e76ce557..aad4bedb6 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -26,6 +26,7 @@ from freqtrade.rpc import RPC from freqtrade.rpc.api_server import ApiServer from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer +from freqtrade.rpc.api_server.webserver_bgwork import ApiBG from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has, log_has_re, patch_get_signal) @@ -1733,7 +1734,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): assert result['status_msg'] == 'Backtest ended' # Simulate running backtest - ApiServer._bgtask_running = True + ApiBG._bgtask_running = True rc = client_get(client, f"{BASE_URI}/backtest/abort") assert_response(rc) result = rc.json() @@ -1762,7 +1763,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): result = rc.json() assert 'Bot Background task already running' in result['error'] - ApiServer._bgtask_running = False + ApiBG._bgtask_running = False # Rerun backtest (should get previous result) rc = client_post(client, f"{BASE_URI}/backtest", data=data)