From 1a1103c23903fa4581975c99875b8833763ddca9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 30 Jul 2023 10:52:23 +0200 Subject: [PATCH] Add backtest-result typing --- freqtrade/data/btanalysis.py | 11 ++++++----- freqtrade/optimize/backtesting.py | 3 ++- .../optimize/optimize_reports/bt_output.py | 5 +++-- .../optimize/optimize_reports/bt_storage.py | 12 ++++++------ .../optimize_reports/optimize_reports.py | 5 +++-- freqtrade/rpc/api_server/api_backtest.py | 3 ++- freqtrade/types/__init__.py | 4 +++- freqtrade/types/backtest_result_type.py | 17 +++++++++++++++++ 8 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 freqtrade/types/backtest_result_type.py diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 12be3c448..625777e48 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -5,7 +5,7 @@ import logging from copy import copy from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Literal, Optional, Union import numpy as np import pandas as pd @@ -15,6 +15,7 @@ from freqtrade.exceptions import OperationalException from freqtrade.misc import json_load from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename from freqtrade.persistence import LocalTrade, Trade, init_db +from freqtrade.types import BacktestResultType logger = logging.getLogger(__name__) @@ -128,7 +129,7 @@ def load_backtest_metadata(filename: Union[Path, str]) -> Dict[str, Any]: raise OperationalException('Unexpected error while loading backtest metadata.') from e -def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]: +def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType: """ Load backtest statistics file. :param filename: pathlib.Path object, or string pointing to the file. @@ -153,14 +154,14 @@ def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]: def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]): """ - Load one strategy from multi-strategy result - and merge it with results + Load one strategy from multi-strategy result and merge it with results :param strategy_name: Name of the strategy contained in the result :param filename: Backtest-result-filename to load :param results: dict to merge the result to. """ bt_data = load_backtest_stats(filename) - for k in ('metadata', 'strategy'): + k: Literal['metadata', 'strategy'] + for k in ('metadata', 'strategy'): # type: ignore results[k][strategy_name] = bt_data[k][strategy_name] comparison = bt_data['strategy_comparison'] for i in range(len(comparison)): diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index d71614442..ce20d2178 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -39,6 +39,7 @@ from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper +from freqtrade.types import BacktestResultType, get_BacktestResultType_default from freqtrade.util.binance_mig import migrate_binance_futures_data from freqtrade.wallets import Wallets @@ -77,7 +78,7 @@ class Backtesting: LoggingMixin.show_output = False self.config = config - self.results: Dict[str, Any] = {} + self.results: BacktestResultType = get_BacktestResultType_default() self.trade_id_counter: int = 0 self.order_id_counter: int = 0 diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index eb30d0c97..532796f4a 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -6,6 +6,7 @@ from tabulate import tabulate from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config from freqtrade.misc import decimals_per_coin, round_coin_value from freqtrade.optimize.optimize_reports.optimize_reports import generate_periodic_breakdown_stats +from freqtrade.types import BacktestResultType logger = logging.getLogger(__name__) @@ -363,7 +364,7 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: print() -def show_backtest_results(config: Config, backtest_stats: Dict): +def show_backtest_results(config: Config, backtest_stats: BacktestResultType): stake_currency = config['stake_currency'] for strategy, results in backtest_stats['strategy'].items(): @@ -383,7 +384,7 @@ def show_backtest_results(config: Config, backtest_stats: Dict): print('\nFor more details, please look at the detail tables above') -def show_sorted_pairlist(config: Config, backtest_stats: Dict): +def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType): if config.get('backtest_show_pair_list', False): for strategy, results in backtest_stats['strategy'].items(): print(f"Pairs for Strategy {strategy}: \n[") diff --git a/freqtrade/optimize/optimize_reports/bt_storage.py b/freqtrade/optimize/optimize_reports/bt_storage.py index d4cee183a..71c6dc130 100644 --- a/freqtrade/optimize/optimize_reports/bt_storage.py +++ b/freqtrade/optimize/optimize_reports/bt_storage.py @@ -1,20 +1,18 @@ import logging -from copy import deepcopy from pathlib import Path from typing import Dict -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 store_backtest_stats( - recordfilename: Path, stats: Dict[str, DataFrame], dtappendix: str) -> None: + recordfilename: Path, stats: BacktestResultType, dtappendix: str) -> None: """ Stores backtest results :param recordfilename: Path object, which can either be a filename or a directory. @@ -33,8 +31,10 @@ def store_backtest_stats( # Store metadata separately. file_dump_json(get_backtest_metadata_filename(filename), stats['metadata']) # Don't mutate the original stats dict. - stats_copy = deepcopy(stats) - del stats_copy['metadata'] + stats_copy = { + 'strategy': stats['strategy'], + 'strategy_comparison': stats['strategy_comparison'], + } file_dump_json(filename, stats_copy) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index f24e30318..b68fc708e 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -11,6 +11,7 @@ from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_ calculate_expectancy, calculate_market_change, calculate_max_drawdown, calculate_sharpe, calculate_sortino) from freqtrade.misc import decimals_per_coin, round_coin_value +from freqtrade.types import BacktestResultType logger = logging.getLogger(__name__) @@ -535,7 +536,7 @@ def generate_strategy_stats(pairlist: List[str], def generate_backtest_stats(btdata: Dict[str, DataFrame], all_results: Dict[str, Dict[str, Union[DataFrame, Dict]]], min_date: datetime, max_date: datetime - ) -> Dict[str, Any]: + ) -> BacktestResultType: """ :param btdata: Backtest data :param all_results: backtest result - dictionary in the form: @@ -544,7 +545,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], :param max_date: Backtest end date :return: Dictionary containing results per strategy and a strategy summary. """ - result: Dict[str, Any] = { + result: BacktestResultType = { 'metadata': {}, 'strategy': {}, 'strategy_comparison': [], diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 9c17bc9e0..6d3174a5a 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -21,6 +21,7 @@ from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, Backtest from freqtrade.rpc.api_server.deps import get_config from freqtrade.rpc.api_server.webserver_bgwork import ApiBG from freqtrade.rpc.rpc import RPCException +from freqtrade.types import get_BacktestResultType_default logger = logging.getLogger(__name__) @@ -69,7 +70,7 @@ def __run_backtest_bg(btconfig: Config): ApiBG.bt['bt'].enable_protections = btconfig.get('enable_protections', False) ApiBG.bt['bt'].strategylist = [strat] - ApiBG.bt['bt'].results = {} + ApiBG.bt['bt'].results = get_BacktestResultType_default() ApiBG.bt['bt'].load_prior_backtest() ApiBG.bt['bt'].abort = False diff --git a/freqtrade/types/__init__.py b/freqtrade/types/__init__.py index 11fe6354b..78883add0 100644 --- a/freqtrade/types/__init__.py +++ b/freqtrade/types/__init__.py @@ -1 +1,3 @@ -from freqtrade.types.valid_exchanges_type import ValidExchangesType # noqa: F401 +# flake8: noqa: F401 +from freqtrade.types.backtest_result_type import BacktestResultType, get_BacktestResultType_default +from freqtrade.types.valid_exchanges_type import ValidExchangesType diff --git a/freqtrade/types/backtest_result_type.py b/freqtrade/types/backtest_result_type.py new file mode 100644 index 000000000..4b2e75ae5 --- /dev/null +++ b/freqtrade/types/backtest_result_type.py @@ -0,0 +1,17 @@ +from typing import Any, Dict, List + +from typing_extensions import TypedDict + + +class BacktestResultType(TypedDict): + metadata: Dict[str, Any] + strategy: Dict[str, Any] + strategy_comparison: List[Any] + + +def get_BacktestResultType_default() -> BacktestResultType: + return { + 'metadata': {}, + 'strategy': {}, + 'strategy_comparison': [], + }