freqtrade/optimize/optimize_reports/bt_storage.py
import logging
from pathlib import Path
from typing import Dict, Optional
from pandas import DataFrame
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.misc import file_dump_joblib, file_dump_json
from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename
from freqtrade.types import BacktestResultType
logger = logging.getLogger(__name__)
def _generate_filename(recordfilename: Path, appendix: str, suffix: str) -> Path:
"""
Generates a filename based on the provided parameters.
:param recordfilename: Path object, which can either be a filename or a directory.
:param appendix: use for the filename. e.g. backtest-result-<datetime>
:param suffix: Suffix to use for the file, e.g. .json, .pkl
:return: Generated filename as a Path object
"""
if recordfilename.is_dir():
filename = (recordfilename / f'backtest-result-{appendix}').with_suffix(suffix)
else:
filename = Path.joinpath(
recordfilename.parent, f'{recordfilename.stem}-{appendix}'
).with_suffix(suffix)
return filename
def store_backtest_stats(
recordfilename: Path, stats: BacktestResultType, dtappendix: str, *,
market_change_data: Optional[DataFrame] = None) -> Path:
"""
Stores backtest results
:param recordfilename: Path object, which can either be a filename or a directory.
Filenames will be appended with a timestamp right before the suffix
while for directories, <directory>/backtest-result-<datetime>.json will be used as filename
:param stats: Dataframe containing the backtesting statistics
:param dtappendix: Datetime to use for the filename
"""
filename = _generate_filename(recordfilename, dtappendix, '.json')
# Store metadata separately.
file_dump_json(get_backtest_metadata_filename(filename), stats['metadata'])
# Don't mutate the original stats dict.
stats_copy = {
'strategy': stats['strategy'],
'strategy_comparison': stats['strategy_comparison'],
}
file_dump_json(filename, stats_copy)
latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN)
file_dump_json(latest_filename, {'latest_backtest': str(filename.name)})
if market_change_data is not None:
filename_mc = _generate_filename(recordfilename, f"{dtappendix}_market_change", '.feather')
market_change_data.reset_index().to_feather(
filename_mc, compression_level=9, compression='lz4')
return filename
def _store_backtest_analysis_data(
recordfilename: Path, data: Dict[str, Dict],
dtappendix: str, name: str) -> Path:
"""
Stores backtest trade candles for analysis
:param recordfilename: Path object, which can either be a filename or a directory.
Filenames will be appended with a timestamp right before the suffix
while for directories, <directory>/backtest-result-<datetime>_<name>.pkl will be used
as filename
:param candles: Dict containing the backtesting data for analysis
:param dtappendix: Datetime to use for the filename
:param name: Name to use for the file, e.g. signals, rejected
"""
filename = _generate_filename(recordfilename, f"{dtappendix}_{name}", '.pkl')
file_dump_joblib(filename, data)
return filename
def store_backtest_analysis_results(
recordfilename: Path, candles: Dict[str, Dict], trades: Dict[str, Dict],
dtappendix: str) -> None:
_store_backtest_analysis_data(recordfilename, candles, dtappendix, "signals")
_store_backtest_analysis_data(recordfilename, trades, dtappendix, "rejected")