stable/freqtrade/rpc/api_server/api_backtest.py

239 lines
8.6 KiB
Python
Raw Permalink Normal View History

2021-04-01 05:55:29 +00:00
import asyncio
import logging
from copy import deepcopy
from datetime import datetime
from typing import Any, Dict, List
2021-04-01 05:55:29 +00:00
from fastapi import APIRouter, BackgroundTasks, Depends
2021-09-27 05:12:40 +00:00
from freqtrade.configuration.config_validation import validate_config_consistency
2022-04-13 04:47:39 +00:00
from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result
2021-04-01 05:55:29 +00:00
from freqtrade.enums import BacktestState
2021-04-05 17:58:53 +00:00
from freqtrade.exceptions import DependencyException
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest,
BacktestResponse)
from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode
2021-04-01 05:55:29 +00:00
from freqtrade.rpc.api_server.webserver import ApiServer
from freqtrade.rpc.rpc import RPCException
logger = logging.getLogger(__name__)
# Private API, protected by authentication
router = APIRouter()
@router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
2022-02-15 19:15:03 +00:00
# flake8: noqa: C901
2021-04-01 05:55:29 +00:00
async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: BackgroundTasks,
config=Depends(get_config), ws_mode=Depends(is_webserver_mode)):
2021-04-01 05:55:29 +00:00
"""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]
try:
btconfig['stake_amount'] = float(btconfig['stake_amount'])
except ValueError:
pass
2021-04-01 05:55:29 +00:00
# Force dry-run for backtesting
btconfig['dry_run'] = True
2021-04-01 05:55:29 +00:00
# Start backtesting
# Initialize backtesting object
def run_backtest():
2022-01-08 07:22:25 +00:00
from freqtrade.optimize.optimize_reports import (generate_backtest_stats,
store_backtest_stats)
2021-04-01 05:55:29 +00:00
from freqtrade.resolvers import StrategyResolver
asyncio.set_event_loop(asyncio.new_event_loop())
try:
# Reload strategy
2021-07-10 09:19:23 +00:00
lastconfig = ApiServer._bt_last_config
2021-04-01 05:55:29 +00:00
strat = StrategyResolver.load_strategy(btconfig)
2021-09-27 05:12:40 +00:00
validate_config_consistency(btconfig)
2021-04-01 05:55:29 +00:00
2021-07-10 09:19:23 +00:00
if (
not ApiServer._bt
or lastconfig.get('timeframe') != strat.timeframe
or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail')
2021-07-20 21:09:09 +00:00
or lastconfig.get('timerange') != btconfig['timerange']
2021-07-10 09:19:23 +00:00
):
2021-04-01 05:55:29 +00:00
from freqtrade.optimize.backtesting import Backtesting
ApiServer._bt = Backtesting(btconfig)
ApiServer._bt.load_bt_data_detail()
else:
ApiServer._bt.config = btconfig
ApiServer._bt.init_backtest()
2021-07-20 21:09:09 +00:00
# Only reload data if timeframe changed.
2021-07-10 09:19:23 +00:00
if (
not ApiServer._bt_data
or not ApiServer._bt_timerange
or lastconfig.get('timeframe') != strat.timeframe
or lastconfig.get('timerange') != btconfig['timerange']
2021-07-10 09:19:23 +00:00
):
ApiServer._bt_data, ApiServer._bt_timerange = ApiServer._bt.load_bt_data()
lastconfig['timerange'] = btconfig['timerange']
lastconfig['timeframe'] = strat.timeframe
lastconfig['protections'] = btconfig.get('protections', [])
lastconfig['enable_protections'] = btconfig.get('enable_protections')
lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet')
2021-04-01 05:55:29 +00:00
ApiServer._bt.strategylist = [strat]
ApiServer._bt.results = {}
ApiServer._bt.load_prior_backtest()
2021-04-05 17:58:53 +00:00
ApiServer._bt.abort = False
if (ApiServer._bt.results and
strat.get_strategy_name() in ApiServer._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.backtest_one_strategy(
strat, ApiServer._bt_data, ApiServer._bt_timerange)
ApiServer._bt.results = generate_backtest_stats(
ApiServer._bt_data, ApiServer._bt.all_results,
min_date=min_date, max_date=max_date)
2022-01-08 07:22:25 +00:00
if btconfig.get('export', 'none') == 'trades':
store_backtest_stats(
btconfig['exportfilename'], ApiServer._bt.results,
datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
)
2022-01-08 07:22:25 +00:00
2021-04-01 17:59:49 +00:00
logger.info("Backtest finished.")
2021-04-01 05:55:29 +00:00
2021-04-05 17:58:53 +00:00
except DependencyException as e:
logger.info(f"Backtesting caused an error: {e}")
pass
2021-04-01 05:55:29 +00:00
finally:
ApiServer._bgtask_running = False
background_tasks.add_task(run_backtest)
ApiServer._bgtask_running = True
return {
"status": "running",
"running": True,
"progress": 0,
"step": str(BacktestState.STARTUP),
"status_msg": "Backtest started",
}
@router.get('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
def api_get_backtest(ws_mode=Depends(is_webserver_mode)):
2021-04-01 05:55:29 +00:00
"""
Get backtesting result.
Returns Result after backtesting has been ran.
"""
from freqtrade.persistence import LocalTrade
if ApiServer._bgtask_running:
return {
"status": "running",
"running": True,
"step": ApiServer._bt.progress.action if ApiServer._bt else str(BacktestState.STARTUP),
"progress": ApiServer._bt.progress.progress if ApiServer._bt else 0,
"trade_count": len(LocalTrade.trades),
"status_msg": "Backtest running",
}
if not ApiServer._bt:
return {
"status": "not_started",
"running": False,
"step": "",
"progress": 0,
2021-04-01 17:59:49 +00:00
"status_msg": "Backtest not yet executed"
2021-04-01 05:55:29 +00:00
}
return {
"status": "ended",
"running": False,
"status_msg": "Backtest ended",
"step": "finished",
"progress": 1,
"backtest_result": ApiServer._bt.results,
}
@router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
def api_delete_backtest(ws_mode=Depends(is_webserver_mode)):
2021-04-01 05:55:29 +00:00
"""Reset backtesting"""
if ApiServer._bgtask_running:
return {
"status": "running",
"running": True,
"step": "",
"progress": 0,
"status_msg": "Backtest running",
}
if ApiServer._bt:
ApiServer._bt.cleanup()
2021-04-01 05:55:29 +00:00
del ApiServer._bt
ApiServer._bt = None
2021-07-10 09:19:23 +00:00
del ApiServer._bt_data
ApiServer._bt_data = None
2021-04-01 05:55:29 +00:00
logger.info("Backtesting reset")
return {
"status": "reset",
"running": False,
"step": "",
"progress": 0,
2021-04-01 17:59:49 +00:00
"status_msg": "Backtest reset",
2021-04-01 05:55:29 +00:00
}
2021-04-05 17:58:53 +00:00
@router.get('/backtest/abort', response_model=BacktestResponse, tags=['webserver', 'backtest'])
def api_backtest_abort(ws_mode=Depends(is_webserver_mode)):
2021-04-05 17:58:53 +00:00
if not ApiServer._bgtask_running:
return {
"status": "not_running",
"running": False,
"step": "",
"progress": 0,
"status_msg": "Backtest ended",
}
ApiServer._bt.abort = True
return {
"status": "stopping",
"running": False,
"step": "",
"progress": 0,
"status_msg": "Backtest ended",
}
@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], tags=['webserver', 'backtest'])
def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)):
# Get backtest result history, read from metadata files
return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results')
@router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest'])
2022-04-13 04:47:39 +00:00
def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)):
# Get backtest result history, read from metadata files
fn = config['user_data_dir'] / 'backtest_results' / filename
results: Dict[str, Any] = {
2022-04-13 04:47:39 +00:00
'metadata': {},
'strategy': {},
'strategy_comparison': [],
}
load_and_merge_backtest_result(strategy, fn, results)
return {
"status": "ended",
"running": False,
"step": "",
"progress": 1,
"status_msg": "Historic result",
2022-04-13 04:47:39 +00:00
"backtest_result": results,
}