Add progress tracking for backtesting
This commit is contained in:
parent
06b6726029
commit
048008756f
@ -1,4 +1,5 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
|
from freqtrade.enums.backteststate import BacktestState
|
||||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
||||||
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
||||||
from freqtrade.enums.selltype import SellType
|
from freqtrade.enums.selltype import SellType
|
||||||
|
14
freqtrade/enums/backteststate.py
Normal file
14
freqtrade/enums/backteststate.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class BacktestState(Enum):
|
||||||
|
"""
|
||||||
|
Bot application states
|
||||||
|
"""
|
||||||
|
STARTUP = 1
|
||||||
|
DATALOAD = 2
|
||||||
|
ANALYZE = 3
|
||||||
|
BACKTEST = 4
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name.lower()}"
|
@ -17,7 +17,7 @@ from freqtrade.data import history
|
|||||||
from freqtrade.data.btanalysis import trade_list_to_dataframe
|
from freqtrade.data.btanalysis import trade_list_to_dataframe
|
||||||
from freqtrade.data.converter import trim_dataframes
|
from freqtrade.data.converter import trim_dataframes
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.enums import SellType
|
from freqtrade.enums import BacktestState, SellType
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||||
from freqtrade.mixins import LoggingMixin
|
from freqtrade.mixins import LoggingMixin
|
||||||
@ -118,11 +118,26 @@ class Backtesting:
|
|||||||
# Get maximum required startup period
|
# Get maximum required startup period
|
||||||
self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
|
self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
|
||||||
|
|
||||||
|
self._progress = 0
|
||||||
|
self._max_steps = 0
|
||||||
|
self._action = BacktestState.STARTUP
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
LoggingMixin.show_output = True
|
LoggingMixin.show_output = True
|
||||||
PairLocks.use_db = True
|
PairLocks.use_db = True
|
||||||
Trade.use_db = True
|
Trade.use_db = True
|
||||||
|
|
||||||
|
def set_progress(self, action: BacktestState, progress: float):
|
||||||
|
self._progress = 0
|
||||||
|
self._action = action
|
||||||
|
|
||||||
|
def get_progress(self) -> float:
|
||||||
|
|
||||||
|
return round(self._progress / self._max_steps, 5) if self._max_steps > 0 else 0
|
||||||
|
|
||||||
|
def get_action(self) -> str:
|
||||||
|
return str(self._action)
|
||||||
|
|
||||||
def _set_strategy(self, strategy: IStrategy):
|
def _set_strategy(self, strategy: IStrategy):
|
||||||
"""
|
"""
|
||||||
Load strategy into backtesting
|
Load strategy into backtesting
|
||||||
@ -145,6 +160,8 @@ class Backtesting:
|
|||||||
Loads backtest data and returns the data combined with the timerange
|
Loads backtest data and returns the data combined with the timerange
|
||||||
as tuple.
|
as tuple.
|
||||||
"""
|
"""
|
||||||
|
self.set_progress(BacktestState.DATALOAD, 0)
|
||||||
|
|
||||||
timerange = TimeRange.parse_timerange(None if self.config.get(
|
timerange = TimeRange.parse_timerange(None if self.config.get(
|
||||||
'timerange') is None else str(self.config.get('timerange')))
|
'timerange') is None else str(self.config.get('timerange')))
|
||||||
|
|
||||||
@ -168,6 +185,7 @@ class Backtesting:
|
|||||||
timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe),
|
timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe),
|
||||||
self.required_startup, min_date)
|
self.required_startup, min_date)
|
||||||
|
|
||||||
|
self.set_progress(BacktestState.DATALOAD, 1)
|
||||||
return data, timerange
|
return data, timerange
|
||||||
|
|
||||||
def prepare_backtest(self, enable_protections):
|
def prepare_backtest(self, enable_protections):
|
||||||
@ -404,6 +422,9 @@ class Backtesting:
|
|||||||
open_trades: Dict[str, List[LocalTrade]] = defaultdict(list)
|
open_trades: Dict[str, List[LocalTrade]] = defaultdict(list)
|
||||||
open_trade_count = 0
|
open_trade_count = 0
|
||||||
|
|
||||||
|
self.set_progress(BacktestState.BACKTEST, 0)
|
||||||
|
self._max_steps = int((end_date - start_date) / timedelta(minutes=self.timeframe_min))
|
||||||
|
|
||||||
# Loop timerange and get candle for each pair at that point in time
|
# Loop timerange and get candle for each pair at that point in time
|
||||||
while tmp <= end_date:
|
while tmp <= end_date:
|
||||||
open_trade_count_start = open_trade_count
|
open_trade_count_start = open_trade_count
|
||||||
@ -463,6 +484,7 @@ class Backtesting:
|
|||||||
self.protections.global_stop(tmp)
|
self.protections.global_stop(tmp)
|
||||||
|
|
||||||
# Move time one configured time_interval ahead.
|
# Move time one configured time_interval ahead.
|
||||||
|
self._progress += 1
|
||||||
tmp += timedelta(minutes=self.timeframe_min)
|
tmp += timedelta(minutes=self.timeframe_min)
|
||||||
|
|
||||||
trades += self.handle_left_open(open_trades, data=data)
|
trades += self.handle_left_open(open_trades, data=data)
|
||||||
@ -478,6 +500,8 @@ class Backtesting:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
|
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
|
||||||
|
self.set_progress(BacktestState.ANALYZE, 0)
|
||||||
|
self._max_steps = 0
|
||||||
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
||||||
backtest_start_time = datetime.now(timezone.utc)
|
backtest_start_time = datetime.now(timezone.utc)
|
||||||
self._set_strategy(strat)
|
self._set_strategy(strat)
|
||||||
@ -504,6 +528,8 @@ class Backtesting:
|
|||||||
"No data left after adjusting for startup candles.")
|
"No data left after adjusting for startup candles.")
|
||||||
|
|
||||||
min_date, max_date = history.get_timerange(preprocessed)
|
min_date, max_date = history.get_timerange(preprocessed)
|
||||||
|
self.set_progress(BacktestState.BACKTEST, 0)
|
||||||
|
|
||||||
logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
|
logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||||
f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
|
f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||||
f'({(max_date - min_date).days} days).')
|
f'({(max_date - min_date).days} days).')
|
||||||
|
@ -329,5 +329,7 @@ class BacktestResponse(BaseModel):
|
|||||||
status: str
|
status: str
|
||||||
running: bool
|
running: bool
|
||||||
status_msg: str
|
status_msg: str
|
||||||
|
step: str
|
||||||
|
progress: float
|
||||||
# TODO: Properly type backtestresult...
|
# TODO: Properly type backtestresult...
|
||||||
backtest_result: Optional[Dict[str, Any]]
|
backtest_result: Optional[Dict[str, Any]]
|
||||||
|
@ -23,6 +23,7 @@ from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, BacktestReques
|
|||||||
WhitelistResponse)
|
WhitelistResponse)
|
||||||
from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional
|
from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional
|
||||||
from freqtrade.rpc.rpc import RPCException
|
from freqtrade.rpc.rpc import RPCException
|
||||||
|
from freqtrade.state import BacktestState
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -292,8 +293,7 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac
|
|||||||
or lastconfig.get('timeframe') != strat.timeframe
|
or lastconfig.get('timeframe') != strat.timeframe
|
||||||
or lastconfig.get('enable_protections') != btconfig.get('enable_protections')
|
or lastconfig.get('enable_protections') != btconfig.get('enable_protections')
|
||||||
or lastconfig.get('protections') != btconfig.get('protections', [])
|
or lastconfig.get('protections') != btconfig.get('protections', [])
|
||||||
or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0)
|
or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0)):
|
||||||
):
|
|
||||||
# TODO: Investigate if enabling protections can be dynamically ingested from here...
|
# TODO: Investigate if enabling protections can be dynamically ingested from here...
|
||||||
from freqtrade.optimize.backtesting import Backtesting
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
ApiServer._bt = Backtesting(btconfig)
|
ApiServer._bt = Backtesting(btconfig)
|
||||||
@ -327,6 +327,8 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac
|
|||||||
return {
|
return {
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"running": True,
|
"running": True,
|
||||||
|
"progress": 0,
|
||||||
|
"step": str(BacktestState.STARTUP),
|
||||||
"status_msg": "Backtest started",
|
"status_msg": "Backtest started",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,6 +343,8 @@ def api_get_backtest():
|
|||||||
return {
|
return {
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"running": True,
|
"running": True,
|
||||||
|
"step": ApiServer._bt.get_action() if ApiServer._bt else str(BacktestState.STARTUP),
|
||||||
|
"progress": ApiServer._bt.get_progress() if ApiServer._bt else 0,
|
||||||
"status_msg": "Backtest running",
|
"status_msg": "Backtest running",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,6 +352,8 @@ def api_get_backtest():
|
|||||||
return {
|
return {
|
||||||
"status": "not_started",
|
"status": "not_started",
|
||||||
"running": False,
|
"running": False,
|
||||||
|
"step": "",
|
||||||
|
"progress": 0,
|
||||||
"status_msg": "Backtesting not yet executed"
|
"status_msg": "Backtesting not yet executed"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,6 +361,8 @@ def api_get_backtest():
|
|||||||
"status": "ended",
|
"status": "ended",
|
||||||
"running": False,
|
"running": False,
|
||||||
"status_msg": "Backtest ended",
|
"status_msg": "Backtest ended",
|
||||||
|
"step": "finished",
|
||||||
|
"progress": 1,
|
||||||
"backtest_result": ApiServer._bt.results,
|
"backtest_result": ApiServer._bt.results,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,6 +374,7 @@ def api_delete_backtest():
|
|||||||
return {
|
return {
|
||||||
"status": "running",
|
"status": "running",
|
||||||
"running": True,
|
"running": True,
|
||||||
|
"progress": 0,
|
||||||
"status_msg": "Backtest running",
|
"status_msg": "Backtest running",
|
||||||
}
|
}
|
||||||
if ApiServer._bt:
|
if ApiServer._bt:
|
||||||
@ -377,5 +386,6 @@ def api_delete_backtest():
|
|||||||
return {
|
return {
|
||||||
"status": "reset",
|
"status": "reset",
|
||||||
"running": False,
|
"running": False,
|
||||||
|
"progress": 0,
|
||||||
"status_msg": "Backtesting reset",
|
"status_msg": "Backtesting reset",
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user