diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index a0f1c05a6..eccfd8157 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -313,3 +313,19 @@ class PairHistory(BaseModel): json_encoders = { datetime: lambda v: v.strftime(DATETIME_PRINT_FORMAT), } + + +class BacktestRequest(BaseModel): + strategy: str + timeframe: Optional[str] + timerange: Optional[str] + max_open_trades: Optional[int] + stake_amount: Optional[int] + + +class BacktestResponse(BaseModel): + status: str + running: bool + status_msg: str + # TODO: Properly type backtestresult... + backtest_result: Optional[Dict[str, Any]] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index dd1df6353..9166a1cee 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -1,8 +1,10 @@ +import asyncio +import logging from copy import deepcopy from pathlib import Path from typing import List, Optional -from fastapi import APIRouter, Depends +from fastapi import APIRouter, BackgroundTasks, Depends from fastapi.exceptions import HTTPException from freqtrade import __version__ @@ -10,18 +12,21 @@ from freqtrade.constants import USERPATH_STRATEGIES from freqtrade.data.history import get_datahandler from freqtrade.exceptions import OperationalException from freqtrade.rpc import RPC -from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, - BlacklistResponse, Count, Daily, - DeleteLockRequest, DeleteTrade, ForceBuyPayload, - ForceBuyResponse, ForceSellPayload, Locks, Logs, - OpenTradeSchema, PairHistory, PerformanceEntry, - Ping, PlotConfig, Profit, ResultMsg, ShowConfig, - Stats, StatusMsg, StrategyListResponse, - StrategyResponse, Version, WhitelistResponse) +from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, BacktestRequest, BacktestResponse, + Balances, BlacklistPayload, BlacklistResponse, + Count, Daily, DeleteLockRequest, DeleteTrade, + ForceBuyPayload, ForceBuyResponse, + ForceSellPayload, Locks, Logs, OpenTradeSchema, + PairHistory, PerformanceEntry, Ping, PlotConfig, + Profit, ResultMsg, ShowConfig, Stats, StatusMsg, + StrategyListResponse, StrategyResponse, Version, + WhitelistResponse) from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional from freqtrade.rpc.rpc import RPCException +logger = logging.getLogger(__name__) + # Public API, requires no auth. router_public = APIRouter() # Private API, protected by authentication @@ -257,3 +262,84 @@ def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Option } return result + +@router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) +async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: BackgroundTasks, + config=Depends(get_config)): + """Start backtesting if not done so already""" + if ApiServer._bgtask_running: + raise RPCException('Bot Background task already running') + + btconfig = deepcopy(config) + settings = dict(bt_settings) + # Pydantic models will contain all keys, but non-provided ones are None + for setting in settings.keys(): + if settings[setting] is not None: + btconfig[setting] = settings[setting] + + # Start backtesting + # Initialize backtesting object + def run_backtest(): + from freqtrade.optimize.backtesting import Backtesting + asyncio.set_event_loop(asyncio.new_event_loop()) + try: + ApiServer._backtesting = Backtesting(btconfig) + ApiServer._backtesting.start() + finally: + ApiServer._bgtask_running = False + + background_tasks.add_task(run_backtest) + ApiServer._bgtask_running = True + + return { + "status": "running", + "running": True, + "status_msg": "Backtest started", + } + + +@router.get('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) +def api_get_backtest(): + """ + Get backtesting result. + Returns Result after backtesting has been ran. + """ + if not ApiServer._backtesting: + return { + "status": "not_started", + "running": False, + "status_msg": "Backtesting not yet executed" + } + if ApiServer._bgtask_running: + return { + "status": "running", + "running": True, + "status_msg": "Backtest running", + } + + return { + "status": "ended", + "running": False, + "status_msg": "Backtest ended", + "backtest_result": ApiServer._backtesting.results, + } + + +@router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) +def api_delete_backtest(): + """Reset backtesting""" + if ApiServer._bgtask_running: + return { + "status": "running", + "running": True, + "status_msg": "Backtest running", + } + if ApiServer._backtesting: + del ApiServer._backtesting + ApiServer._backtesting = None + logger.info("Backtesting reset") + return { + "status": "reset", + "running": False, + "status_msg": "Backtesting reset", + } diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index f53c8b0c6..f2ebf785f 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -33,6 +33,8 @@ class ApiServer(RPCHandler): __initialized = False _rpc: RPC + # Backtesting type: Backtesting + _backtesting = None _has_rpc: bool = False _bgtask_running: bool = False _config: Dict[str, Any] = {}