|
|
|
@@ -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)):
|
|
|
|
|
ApiBG._bt['bt_error'] = None
|
|
|
|
|
ApiBG.bt['bt_error'] = None
|
|
|
|
|
"""Start backtesting if not done so already"""
|
|
|
|
|
if ApiBG._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 = ApiBG._bt['last_config']
|
|
|
|
|
lastconfig = ApiBG.bt['last_config']
|
|
|
|
|
strat = StrategyResolver.load_strategy(btconfig)
|
|
|
|
|
validate_config_consistency(btconfig)
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
not ApiBG._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
|
|
|
|
|
ApiBG._bt['bt'] = Backtesting(btconfig)
|
|
|
|
|
ApiBG._bt['bt'].load_bt_data_detail()
|
|
|
|
|
ApiBG.bt['bt'] = Backtesting(btconfig)
|
|
|
|
|
ApiBG.bt['bt'].load_bt_data_detail()
|
|
|
|
|
else:
|
|
|
|
|
ApiBG._bt['bt'].config = btconfig
|
|
|
|
|
ApiBG._bt['bt'].init_backtest()
|
|
|
|
|
ApiBG.bt['bt'].config = btconfig
|
|
|
|
|
ApiBG.bt['bt'].init_backtest()
|
|
|
|
|
# Only reload data if timeframe changed.
|
|
|
|
|
if (
|
|
|
|
|
not ApiBG._bt['data']
|
|
|
|
|
or not ApiBG._bt['timerange']
|
|
|
|
|
not ApiBG.bt['data']
|
|
|
|
|
or not ApiBG.bt['timerange']
|
|
|
|
|
or lastconfig.get('timeframe') != strat.timeframe
|
|
|
|
|
or lastconfig.get('timerange') != btconfig['timerange']
|
|
|
|
|
):
|
|
|
|
|
ApiBG._bt['data'], ApiBG._bt['timerange'] = ApiBG._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')
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
ApiBG._bt['bt'].abort = False
|
|
|
|
|
if (ApiBG._bt['bt'].results and
|
|
|
|
|
strat.get_strategy_name() in ApiBG._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 = ApiBG._bt['bt'].backtest_one_strategy(
|
|
|
|
|
strat, ApiBG._bt['data'], ApiBG._bt['timerange'])
|
|
|
|
|
min_date, max_date = ApiBG.bt['bt'].backtest_one_strategy(
|
|
|
|
|
strat, ApiBG.bt['data'], ApiBG.bt['timerange'])
|
|
|
|
|
|
|
|
|
|
ApiBG._bt['bt'].results = generate_backtest_stats(
|
|
|
|
|
ApiBG._bt['data'], ApiBG._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'], ApiBG._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}")
|
|
|
|
|
ApiBG._bt['bt_error'] = str(e)
|
|
|
|
|
ApiBG.bt['bt_error'] = str(e)
|
|
|
|
|
pass
|
|
|
|
|
finally:
|
|
|
|
|
ApiBG._bgtask_running = False
|
|
|
|
|
ApiBG.bgtask_running = False
|
|
|
|
|
|
|
|
|
|
background_tasks.add_task(run_backtest)
|
|
|
|
|
ApiBG._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 ApiBG._bgtask_running:
|
|
|
|
|
if ApiBG.bgtask_running:
|
|
|
|
|
return {
|
|
|
|
|
"status": "running",
|
|
|
|
|
"running": True,
|
|
|
|
|
"step": (ApiBG._bt['bt'].progress.action if ApiBG._bt['bt']
|
|
|
|
|
"step": (ApiBG.bt['bt'].progress.action if ApiBG.bt['bt']
|
|
|
|
|
else str(BacktestState.STARTUP)),
|
|
|
|
|
"progress": ApiBG._bt['bt'].progress.progress if ApiBG._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 ApiBG._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 ApiBG._bt['bt_error']:
|
|
|
|
|
if ApiBG.bt['bt_error']:
|
|
|
|
|
return {
|
|
|
|
|
"status": "error",
|
|
|
|
|
"running": False,
|
|
|
|
|
"step": "",
|
|
|
|
|
"progress": 0,
|
|
|
|
|
"status_msg": f"Backtest failed with {ApiBG._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": ApiBG._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 ApiBG._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 ApiBG._bt['bt']:
|
|
|
|
|
ApiBG._bt['bt'].cleanup()
|
|
|
|
|
del ApiBG._bt['bt']
|
|
|
|
|
ApiBG._bt['bt'] = None
|
|
|
|
|
del ApiBG._bt['data']
|
|
|
|
|
ApiBG._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 ApiBG._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",
|
|
|
|
|
}
|
|
|
|
|
ApiBG._bt['bt'].abort = True
|
|
|
|
|
ApiBG.bt['bt'].abort = True
|
|
|
|
|
return {
|
|
|
|
|
"status": "stopping",
|
|
|
|
|
"running": False,
|
|
|
|
|