From a9ef4c3ab013b6e7a6f953788ce221b8f5301ea7 Mon Sep 17 00:00:00 2001 From: hippocritical Date: Wed, 12 Apr 2023 21:03:59 +0200 Subject: [PATCH] partial progress commit: added terminal tabulate-output added yet non-working csv output using pandas --- freqtrade/commands/arguments.py | 10 ++- freqtrade/commands/cli_options.py | 5 ++ freqtrade/commands/strategy_utils_commands.py | 78 ++++++++++++++++--- .../backtest_lookahead_bias_checker.py | 68 +++++++++------- 4 files changed, 116 insertions(+), 45 deletions(-) mode change 100644 => 100755 freqtrade/commands/arguments.py mode change 100644 => 100755 freqtrade/commands/cli_options.py mode change 100644 => 100755 freqtrade/commands/strategy_utils_commands.py mode change 100644 => 100755 freqtrade/strategy/backtest_lookahead_bias_checker.py diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py old mode 100644 new mode 100755 index 6cb727eaf..ac5c33ad1 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -116,10 +116,11 @@ NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"] -ARGS_STRATEGY_UPDATER = ["strategy_list"] +ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_search"] ARGS_BACKTEST_LOOKAHEAD_BIAS_CHECKER = ARGS_BACKTEST + ["minimum_trade_amount", - "targeted_trade_amount"] + "targeted_trade_amount", + "overwrite_existing_exportfilename_content"] # + ["target_trades", "minimum_trades", @@ -458,13 +459,14 @@ class Arguments: 'files to the current version', parents=[_common_parser]) strategy_updater_cmd.set_defaults(func=start_strategy_update) - self._build_args(optionlist=ARGS_STRATEGY_UPDATER, parser=strategy_updater_cmd) + self._build_args(optionlist=ARGS_STRATEGY_UPDATER, + parser=strategy_updater_cmd) # Add backtest lookahead bias checker subcommand backtest_lookahead_bias_checker_cmd = \ subparsers.add_parser('backtest-lookahead-bias-checker', help="checks for potential look ahead bias", - parents=[_common_parser]) + parents=[_common_parser, _strategy_parser]) backtest_lookahead_bias_checker_cmd.set_defaults(func=start_backtest_lookahead_bias_checker) self._build_args(optionlist=ARGS_BACKTEST_LOOKAHEAD_BIAS_CHECKER, diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py old mode 100644 new mode 100755 index 5d2af934f..e0709fc31 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -688,5 +688,10 @@ AVAILABLE_CLI_OPTIONS = { type=check_int_positive, metavar='INT', default=20, + ), + "overwrite_existing_exportfilename_content": Arg( + '--overwrite-existing-exportfilename-content', + help='overwrites existing contents if existent with exportfilename given', + action='store_true' ) } diff --git a/freqtrade/commands/strategy_utils_commands.py b/freqtrade/commands/strategy_utils_commands.py old mode 100644 new mode 100755 index 663ea571a..b46481734 --- a/freqtrade/commands/strategy_utils_commands.py +++ b/freqtrade/commands/strategy_utils_commands.py @@ -4,6 +4,9 @@ import time from pathlib import Path from typing import Any, Dict +import pandas as pd +from tabulate import tabulate + from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.resolvers import StrategyResolver @@ -76,7 +79,6 @@ def start_backtest_lookahead_bias_checker(args: Dict[str, Any]) -> None: config, enum_failed=False, recursive=config.get('recursive_strategy_search', False)) bias_checker_instances = [] - filtered_strategy_objs = [] if 'strategy_list' in args and args['strategy_list'] is not None: for args_strategy in args['strategy_list']: @@ -87,28 +89,82 @@ def start_backtest_lookahead_bias_checker(args: Dict[str, Any]) -> None: break for filtered_strategy_obj in filtered_strategy_objs: - bias_checker_instances = initialize_single_lookahead_bias_checker( - filtered_strategy_obj, config, args) + bias_checker_instances.append( + initialize_single_lookahead_bias_checker(filtered_strategy_obj, config, args)) else: processed_locations = set() for strategy_obj in strategy_objs: if strategy_obj['location'] not in processed_locations: processed_locations.add(strategy_obj['location']) - bias_checker_instances = initialize_single_lookahead_bias_checker( - strategy_obj, config, args) - create_result_list(bias_checker_instances) + bias_checker_instances.append( + initialize_single_lookahead_bias_checker(strategy_obj, config, args)) + text_table_bias_checker_instances(bias_checker_instances) + export_to_csv(args, bias_checker_instances) -def create_result_list(bias_checker_instances): - pass +def text_table_bias_checker_instances(bias_checker_instances): + headers = ['strategy', 'has_bias', + 'total_signals', 'biased_entry_signals', 'biased_exit_signals', 'biased_indicators'] + data = [] + for current_instance in bias_checker_instances: + data.append( + [current_instance.strategy_obj['name'], + current_instance.current_analysis.has_bias, + current_instance.current_analysis.total_signals, + current_instance.current_analysis.false_entry_signals, + current_instance.current_analysis.false_exit_signals, + ", ".join(current_instance.current_analysis.false_indicators)] + ) + table = tabulate(data, headers=headers, tablefmt="orgtbl") + print(table) + + +def export_to_csv(args, bias_checker_instances): + def add_or_update_row(df, row_data): + strategy_col_name = 'strategy' + if row_data[strategy_col_name] in df[strategy_col_name].values: + # create temporary dataframe with a single row + # and use that to replace the previous data in there. + index = (df.index[df[strategy_col_name] == + row_data[strategy_col_name]][0]) + df.loc[index] = pd.Series(row_data, index='strategy') + + else: + df = df.concat(row_data, ignore_index=True) + return df + + csv_df = None + + if not Path.exists(args['exportfilename']): + # If the file doesn't exist, create a new DataFrame from scratch + csv_df = pd.DataFrame(columns=['filename', 'strategy', 'has_bias', + 'total_signals', + 'biased_entry_signals', 'biased_exit_signals', + 'biased_indicators'], + index='filename') + else: + # Read CSV file into a pandas dataframe + csv_df = pd.read_csv(args['exportfilename']) + + for inst in bias_checker_instances: + new_row_data = {'filename': inst.strategy_obj['location'].parts[-1], + 'strategy': inst.strategy_obj['name'], + 'has_bias': inst.current_analysis.has_bias, + 'total_signals': inst.current_analysis.total_signals, + 'biased_entry_signals': inst.current_analysis.false_entry_signals, + 'biased_exit_signals': inst.current_analysis.false_exit_signals, + 'biased_indicators': ", ".join(inst.current_analysis.false_indicators)} + csv_df = add_or_update_row(csv_df, new_row_data) + if len(bias_checker_instances) > 0: + print(f"saving {args['exportfilename']}") + csv_df.to_csv(args['exportfilename']) def initialize_single_lookahead_bias_checker(strategy_obj, config, args): - # try: print(f"Bias test of {Path(strategy_obj['location']).name} started.") - instance_backtest_lookahead_bias_checker = BacktestLookaheadBiasChecker() start = time.perf_counter() - current_instance = instance_backtest_lookahead_bias_checker.start(config, strategy_obj, args) + current_instance = BacktestLookaheadBiasChecker() + current_instance.start(config, strategy_obj, args) elapsed = time.perf_counter() - start print(f"checking look ahead bias via backtests of {Path(strategy_obj['location']).name} " f"took {elapsed:.1f} seconds.") diff --git a/freqtrade/strategy/backtest_lookahead_bias_checker.py b/freqtrade/strategy/backtest_lookahead_bias_checker.py old mode 100644 new mode 100755 index c4a321a4a..c48c3a826 --- a/freqtrade/strategy/backtest_lookahead_bias_checker.py +++ b/freqtrade/strategy/backtest_lookahead_bias_checker.py @@ -2,7 +2,7 @@ import copy from copy import deepcopy from datetime import datetime, timedelta, timezone -import pandas +from pandas import DataFrame from freqtrade.configuration import TimeRange from freqtrade.data.history import get_timerange @@ -10,33 +10,37 @@ from freqtrade.exchange import timeframe_to_minutes from freqtrade.optimize.backtesting import Backtesting +class VarHolder: + timerange: TimeRange + data: DataFrame + indicators: DataFrame + result: DataFrame + compared: DataFrame + from_dt: datetime + to_dt: datetime + compared_dt: datetime + + +class Analysis: + def __init__(self): + self.total_signals = 0 + self.false_entry_signals = 0 + self.false_exit_signals = 0 + self.false_indicators = [] + self.has_bias = False + + total_signals: int + false_entry_signals: int + false_exit_signals: int + + false_indicators: list + has_bias: bool + + class BacktestLookaheadBiasChecker: - class VarHolder: - timerange: TimeRange - data: pandas.DataFrame - indicators: pandas.DataFrame - result: pandas.DataFrame - compared: pandas.DataFrame - from_dt: datetime - to_dt: datetime - compared_dt: datetime - - class Analysis: - def __init__(self): - self.total_signals = 0 - self.false_entry_signals = 0 - self.false_exit_signals = 0 - self.false_indicators = [] - self.has_bias = False - - total_signals: int - false_entry_signals: int - false_exit_signals: int - - false_indicators: list - has_bias: bool def __init__(self): + self.exportfilename = None self.strategy_obj = None self.current_analysis = None self.local_config = None @@ -44,7 +48,6 @@ class BacktestLookaheadBiasChecker: self.entry_varHolder = None self.exit_varHolder = None self.backtesting = None - self.current_analysis = None self.minimum_trade_amount = None self.targeted_trade_amount = None @@ -124,9 +127,12 @@ class BacktestLookaheadBiasChecker: prepare_data_config['timerange'] = (str(self.dt_to_timestamp(varHolder.from_dt)) + "-" + str(self.dt_to_timestamp(varHolder.to_dt))) prepare_data_config['pairs'] = pairs_to_load + self.backtesting = Backtesting(prepare_data_config) self.backtesting._set_strategy(self.backtesting.strategylist[0]) varHolder.data, varHolder.timerange = self.backtesting.load_bt_data() + self.backtesting.load_bt_data_detail() + varHolder.indicators = self.backtesting.strategy.advise_all_indicators(varHolder.data) varHolder.result = self.get_result(self.backtesting, varHolder.indicators) @@ -139,12 +145,14 @@ class BacktestLookaheadBiasChecker: # and not worry about another strategy to check after. self.local_config = deepcopy(config) self.local_config['strategy_list'] = [strategy_obj['name']] - self.current_analysis = BacktestLookaheadBiasChecker.Analysis() + self.current_analysis = Analysis() self.minimum_trade_amount = args['minimum_trade_amount'] self.targeted_trade_amount = args['targeted_trade_amount'] + self.exportfilename = args['exportfilename'] + self.strategy_obj = strategy_obj # first make a single backtest - self.full_varHolder = BacktestLookaheadBiasChecker.VarHolder() + self.full_varHolder = VarHolder() # define datetime in human-readable format parsed_timerange = TimeRange.parse_timerange(config['timerange']) @@ -182,8 +190,8 @@ class BacktestLookaheadBiasChecker: self.current_analysis.total_signals += 1 - self.entry_varHolder = BacktestLookaheadBiasChecker.VarHolder() - self.exit_varHolder = BacktestLookaheadBiasChecker.VarHolder() + self.entry_varHolder = VarHolder() + self.exit_varHolder = VarHolder() self.entry_varHolder.from_dt = self.full_varHolder.from_dt self.entry_varHolder.compared_dt = result_row['open_date']