diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 0ce9b577b..83c18a205 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -12,7 +12,7 @@ from freqtrade.commands.data_commands import (start_convert_data, start_download start_list_data) from freqtrade.commands.deploy_commands import (start_create_userdir, start_install_ui, start_new_hyperopt, start_new_strategy) -from freqtrade.commands.automation_commands import start_build_hyperopt +from freqtrade.commands.automation_commands import (start_build_hyperopt, start_custom_hyperopt, start_extract_strategy) from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hyperopt_show from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts, start_list_markets, start_list_strategies, diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 2393a39b0..5b9babccc 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -55,8 +55,13 @@ ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"] +# Automation ARGS_BUILD_CUSTOM_HYPEROPT = ["buy_indicators", "sell_indicators", "hyperopt"] +ARGS_EXTRACT_STRATEGY = ["strategy", "extract_name"] + +ARGS_BUILD_BUILD_HYPEROPT = ["strategy", "hyperopt"] + ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"] ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"] @@ -175,7 +180,7 @@ class Arguments: start_list_data, start_list_exchanges, start_list_hyperopts, start_list_markets, start_list_strategies, start_list_timeframes, start_new_config, start_new_hyperopt, - start_build_hyperopt, + start_build_hyperopt, start_custom_hyperopt, start_extract_strategy, start_new_strategy, start_plot_dataframe, start_plot_profit, start_show_trades, start_test_pairlist, start_trading) @@ -210,12 +215,24 @@ class Arguments: build_hyperopt_cmd.set_defaults(func=start_new_hyperopt) self._build_args(optionlist=ARGS_BUILD_HYPEROPT, parser=build_hyperopt_cmd) - # add build-hyperopt subcommand - build_custom_hyperopt_cmd = subparsers.add_parser('build-hyperopt', + # add custom-hyperopt subcommand + build_custom_hyperopt_cmd = subparsers.add_parser('custom-hyperopt', help="Build a custom hyperopt") - build_custom_hyperopt_cmd.set_defaults(func=start_build_hyperopt) + build_custom_hyperopt_cmd.set_defaults(func=start_custom_hyperopt) self._build_args(optionlist=ARGS_BUILD_CUSTOM_HYPEROPT, parser=build_custom_hyperopt_cmd) + # add extract-strategy subcommand + extract_strategy_cmd = subparsers.add_parser('extract-strategy', + help="Extract data dictionaries for custom-hyperopt from strategy") + extract_strategy_cmd.set_defaults(func=start_extract_strategy) + self._build_args(optionlist=ARGS_EXTRACT_STRATEGY, parser=extract_strategy_cmd) + + # add build-hyperopt subcommand + build_extracted_hyperopt_cmd = subparsers.add_parser('build-hyperopt', + help="Create a hyperopt for a strategy") + build_extracted_hyperopt_cmd.set_defaults(func=start_build_hyperopt) + self._build_args(optionlist=ARGS_BUILD_BUILD_HYPEROPT, parser=build_extracted_hyperopt_cmd) + # add new-strategy subcommand build_strategy_cmd = subparsers.add_parser('new-strategy', help="Create new strategy") diff --git a/freqtrade/commands/automation_commands.py b/freqtrade/commands/automation_commands.py index a639a8fa2..2e65e2076 100644 --- a/freqtrade/commands/automation_commands.py +++ b/freqtrade/commands/automation_commands.py @@ -1,9 +1,10 @@ import ast import logging from pathlib import Path -from typing import Any, Dict +from typing import Any, Dict, List -from freqtrade.constants import USERPATH_HYPEROPTS +from freqtrade.constants import (USERPATH_HYPEROPTS, + USERPATH_STRATEGIES) from freqtrade.exceptions import OperationalException from freqtrade.state import RunMode from freqtrade.configuration import setup_utils_configuration @@ -11,20 +12,139 @@ from freqtrade.misc import render_template logger = logging.getLogger(__name__) -''' - TODO - -make the code below more dynamic with a large list of indicators and aims - -buy_space integer values variation based on aim(later deep learning) - -add --mode , see notes - -when making the strategy reading tool, make sure that the populate indicators gets copied to here -''' -POSSIBLE_GUARDS = ["rsi", "mfi", "fastd"] -POSSIBLE_TRIGGERS = ["bb_lowerband", "bb_upperband"] -POSSIBLE_VALUES = {"above": ">", "below": "<"} +# ---------------------------------------------------extract-strategy------------------------------------------------------ + +def get_indicator_info(file: List, indicators: Dict) -> None: + """ + Get all necessary information to build a custom hyperopt space using + the file and a dictionary filled with the indicators and their corropsonding line numbers. + """ + info_list = [] + for indicator in indicators: + indicator_info = [] + + # find the corrosponding aim + for position, line in enumerate(file): + if position == indicators[indicator]: + # use split twice to remove the context around the indicator + back_of_line = line.split(f"(dataframe['{indicator}'] ", 1)[1] + aim = back_of_line.split()[0] + + # add the indicator and aim to the info + indicator_info.append(indicator) + indicator_info.append(aim) + + # check if first character after aim is a d in which case the indicator is a trigger + if back_of_line.split()[1][0] == "d": + indicator_info.append("trigger") + + # add the second indicator of the guard to the info list + back_of_line = back_of_line.split("dataframe['")[1] + second_indicator = back_of_line.split("'])")[0] + indicator_info.append(second_indicator) + + # elif indicator[0:3] == "CDL": + # indicator_info.append("guard") + + # else it is a regular guard + else: + indicator_info.append("guard") + + value = back_of_line.split()[1] + value = value[:-1] + value = float(value) + + indicator_info.append(value) + info_list.append(indicator_info) + + return info_list -def build_hyperopt_buyelements(buy_indicators: Dict[str, str]): +def extract_lists(strategypath: Path) -> None: + """ + Get the indicators, their aims and the stoploss and format them into lists + """ + + # store the file in a list for reference + stored_file = [] + with open(strategypath) as file: + for line in file: + stored_file.append(line) + + # find the start and end of buy trend + for position, line in enumerate(stored_file): + if "populate_buy_trend(" in line: + start_buy_number = position + elif "populate_sell_trend(" in line: + end_buy_number = position + + # list the numbers between the start and end of buy trend + buy_lines = [] + for i in range(start_buy_number, end_buy_number): + buy_lines.append(i) + + # populate the indicators dictionaries with indicators attached to the line they are on + buyindicators = {} + sellindicators = {} + + for position, line in enumerate(stored_file): + # check the lines in buy trend for indicator and add them + if position in buy_lines and "(dataframe['" in line: + # use split twice to remove the context around the indicator + back_of_line = line.split("(dataframe['", 1)[1] + buyindicator = back_of_line.split("'] ", 1)[0] + buyindicators[buyindicator] = position + + # check the lines in sell trend for indicator and add them + elif position > end_buy_number and "(dataframe['" in line: + # use split twice to remove the context around the indicator + back_of_line = line.split("(dataframe['", 1)[1] + sellindicator = back_of_line.split("'] ", 1)[0] + sellindicators[sellindicator] = position + + # build the final lists + buy_info_list = get_indicator_info(stored_file, buyindicators) + sell_info_list = get_indicator_info(stored_file, sellindicators) + + # put the final lists into a tuple + final_lists = (buy_info_list, sell_info_list) + + return final_lists + + +def start_extract_strategy(args: Dict) -> None: + """ + Check if the right subcommands where passed and start extracting the strategy data + """ + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + # check if all required options are filled in + if not 'strategy' in args or not args['strategy']: + raise OperationalException("`extract-strategy` requires --strategy to be set.") + else: + # if the name is not specified use (strategy)_extract + if not 'extract_name' in args or not args['extract_name']: + args['extract_name'] = args['strategy'] + "_extract" + + new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['extract_name'] + '.txt') + if new_path.exists(): + raise OperationalException(f"`{new_path}` already exists. " + "Please choose another name.") + # the path of the chosen strategy + strategy_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py') + + # extract the buy and sell indicators as dicts + extracted_lists = str(extract_lists(strategy_path)) + + # save the dicts in a file + logger.info(f"Writing custom hyperopt to `{new_path}`.") + new_path.write_text(extracted_lists) + + +# --------------------------------------------------custom-hyperopt------------------------------------------------------ + +def custom_hyperopt_buyelements(buy_indicators: List): """ Build the arguments with the placefillers for the buygenerator """ @@ -32,39 +152,44 @@ def build_hyperopt_buyelements(buy_indicators: Dict[str, str]): buy_triggers = "" buy_space = "" - for indicator in buy_indicators: - # Error handling - if not indicator in POSSIBLE_GUARDS and not indicator in POSSIBLE_TRIGGERS: - raise OperationalException( - f"`{indicator}` is not part of the available indicators. The current options are {POSSIBLE_GUARDS + POSSIBLE_TRIGGERS}.") - elif not buy_indicators[indicator] in POSSIBLE_VALUES: - raise OperationalException( - f"`{buy_indicators[indicator]}` is not part of the available indicator options. The current options are {POSSIBLE_VALUES}.") + for indicator_info in buy_indicators: + indicator = indicator_info[0] + aim = indicator_info[1] + usage = indicator_info[2] + # If the indicator is a guard - elif indicator in POSSIBLE_GUARDS: - # get the symbol corrosponding to the value - aim = POSSIBLE_VALUES[buy_indicators[indicator]] + if usage == "guard": + value = indicator_info[3] + + if value >= -1.0 and value <= 1.0: + lower_bound = value - 0.3 + upper_bound = value + 0.3 + else: + lower_bound = value - 30.0 + upper_bound = value + 30.0 # add the guard to its argument - buy_guards += f"if '{indicator}-enabled' in params and params['{indicator}-enabled']: conditions.append(dataframe['{indicator}'] {aim} params['{indicator}-value'])" + buy_guards += f"if params.get('{indicator}-enabled'):\n conditions.append(dataframe['{indicator}'] {aim} params['{indicator}-value'])\n" # add the space to its argument - buy_space += f"Integer(10, 90, name='{indicator}-value'), Categorical([True, False], name='{indicator}-enabled')," - # If the indicator is a trigger - elif indicator in POSSIBLE_TRIGGERS: - # get the symbol corrosponding to the value - aim = POSSIBLE_VALUES[buy_indicators[indicator]] + buy_space += f"Integer({lower_bound}, {upper_bound}, name='{indicator}-value'),\nCategorical([True, False], name='{indicator}-enabled'),\n" + # If the indicator is a trigger + elif usage == "trigger": + secondindicator = indicator_info[3] # add the trigger to its argument - buy_triggers += f"if params['trigger'] == '{indicator}': conditions.append(dataframe['{indicator}'] {aim} dataframe['close'])" + buy_triggers += f"if params['trigger'] == '{indicator}':\n conditions.append(dataframe['{indicator}'] {aim} dataframe['{secondindicator}'])\n" # Final line of indicator space makes all triggers - + buy_space += "Categorical([" # adding all triggers to the list - for indicator in buy_indicators: - if indicator in POSSIBLE_TRIGGERS: + for indicator_info in buy_indicators: + indicator = indicator_info[0] + usage = indicator_info[2] + + if usage == "trigger": buy_space += f"'{indicator}', " # Deleting the last ", " @@ -74,7 +199,7 @@ def build_hyperopt_buyelements(buy_indicators: Dict[str, str]): return {"buy_guards": buy_guards, "buy_triggers": buy_triggers, "buy_space": buy_space} -def build_hyperopt_sellelements(sell_indicators: Dict[str, str]): +def custom_hyperopt_sellelements(sell_indicators: Dict[str, str]): """ Build the arguments with the placefillers for the sellgenerator """ @@ -82,44 +207,50 @@ def build_hyperopt_sellelements(sell_indicators: Dict[str, str]): sell_triggers = "" sell_space = "" - for indicator in sell_indicators: - # Error handling - if not indicator in POSSIBLE_GUARDS and not indicator in POSSIBLE_TRIGGERS: - raise OperationalException( - f"`{indicator}` is not part of the available indicators. The current options are {POSSIBLE_GUARDS + POSSIBLE_TRIGGERS}.") - elif not sell_indicators[indicator] in POSSIBLE_VALUES: - raise OperationalException( - f"`{sell_indicators[indicator]}` is not part of the available indicator options. The current options are {POSSIBLE_VALUES}.") - # If indicator is a guard - elif indicator in POSSIBLE_GUARDS: - # get the symbol corrosponding to the value - aim = POSSIBLE_VALUES[sell_indicators[indicator]] + for indicator_info in sell_indicators: + indicator = indicator_info[0] + aim = indicator_info[1] + usage = indicator_info[2] + + # If the indicator is a guard + if usage == "guard": + value = indicator_info[3] + + if value >= -1 and value <= 1: + lower_bound = value - 0.3 + upper_bound = value + 0.3 + else: + lower_bound = value - 30 + upper_bound = value + 30 # add the guard to its argument - sell_guards += f"if '{indicator}-enabled' in params and params['sell-{indicator}-enabled']: conditions.append(dataframe['{indicator}'] {aim} params['sell-{indicator}-value'])" + sell_guards += f"if params.get('sell-{indicator}-enabled'):\n conditions.append(dataframe['{indicator}'] {aim} params['sell-{indicator}-value'])\n" # add the space to its argument - sell_space += f"Integer(10, 90, name='sell-{indicator}-value'), Categorical([True, False], name='sell-{indicator}-enabled')," + sell_space += f"Integer({lower_bound}, {upper_bound}, name='sell-{indicator}-value'),\nCategorical([True, False], name='sell-{indicator}-enabled'),\n" + # If the indicator is a trigger - elif indicator in POSSIBLE_TRIGGERS: - # get the symbol corrosponding to the value - aim = POSSIBLE_VALUES[sell_indicators[indicator]] + elif usage == "trigger": + secondindicator = indicator_info[3] # add the trigger to its argument - sell_triggers += f"if params['sell-trigger'] == 'sell-{indicator}': conditions.append(dataframe['{indicator}'] {aim} dataframe['close'])" + sell_triggers += f"if params['sell-trigger'] == 'sell-{indicator}':\n conditions.append(dataframe['{indicator}'] {aim} dataframe['{secondindicator}'])\n" # Final line of indicator space makes all triggers sell_space += "Categorical([" - # Adding all triggers to the list - for indicator in sell_indicators: - if indicator in POSSIBLE_TRIGGERS: + # adding all triggers to the list + for indicator_info in sell_indicators: + indicator = indicator_info[0] + usage = indicator_info[2] + + if usage == "trigger": sell_space += f"'sell-{indicator}', " # Deleting the last ", " sell_space = sell_space[:-2] - sell_space += "], name='trigger')" + sell_space += "], name='sell-trigger')" return {"sell_guards": sell_guards, "sell_triggers": sell_triggers, "sell_space": sell_space} @@ -130,11 +261,11 @@ def deploy_custom_hyperopt(hyperopt_name: str, hyperopt_path: Path, buy_indicato """ # Build the arguments for the buy and sell generators - buy_args = build_hyperopt_buyelements(buy_indicators) - sell_args = build_hyperopt_sellelements(sell_indicators) + buy_args = custom_hyperopt_buyelements(buy_indicators) + sell_args = custom_hyperopt_sellelements(sell_indicators) # Build the final template - strategy_text = render_template(templatefile='base_hyperopt.py.j2', + strategy_text = render_template(templatefile='base_custom_hyperopt.py.j2', arguments={"hyperopt": hyperopt_name, "buy_guards": buy_args["buy_guards"], "buy_triggers": buy_args["buy_triggers"], @@ -148,19 +279,18 @@ def deploy_custom_hyperopt(hyperopt_name: str, hyperopt_path: Path, buy_indicato hyperopt_path.write_text(strategy_text) -def start_build_hyperopt(args: Dict[str, Any]) -> None: +def start_custom_hyperopt(args: Dict[str, Any]) -> None: """ Check if the right subcommands where passed and start building the hyperopt """ config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) - # check what the name of the hyperopt should if not 'hyperopt' in args or not args['hyperopt']: - raise OperationalException("`build-hyperopt` requires --hyperopt to be set.") + raise OperationalException("`custom-hyperopt` requires --hyperopt to be set.") elif not 'buy_indicators' in args or not args['buy_indicators']: - raise OperationalException("`build-hyperopt` requires --buy-indicators to be set.") + raise OperationalException("`custom-hyperopt` requires --buy-indicators to be set.") elif not 'sell_indicators' in args or not args['sell_indicators']: - raise OperationalException("`build-hyperopt` requires --sell-indicators to be set.") + raise OperationalException("`custom-hyperopt` requires --sell-indicators to be set.") else: if args['hyperopt'] == 'DefaultHyperopt': raise OperationalException("DefaultHyperopt is not allowed as name.") @@ -175,3 +305,40 @@ def start_build_hyperopt(args: Dict[str, Any]) -> None: deploy_custom_hyperopt(args['hyperopt'], new_path, buy_indicators, sell_indicators) + + +# --------------------------------------------------build-hyperopt------------------------------------------------------ + +def start_build_hyperopt(args: Dict[str, Any]) -> None: + """ + Check if the right subcommands where passed and start building the hyperopt + """ + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + # strategy and hyperopt need to be defined + if not 'strategy' in args or not args['strategy']: + raise OperationalException("`build-hyperopt` requires --strategy to be set.") + if not 'hyperopt' in args or not args['hyperopt']: + args['hyperopt'] = args['strategy'] + "opt" + else: + if args['hyperopt'] == 'DefaultHyperopt': + raise OperationalException("DefaultHyperopt is not allowed as name.") + + # the path of the chosen strategy + strategy_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py') + + # the path where the hyperopt should be written + new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py') + if new_path.exists(): + raise OperationalException(f"`{new_path}` already exists. " + "Please choose another Hyperopt Name.") + + # extract the buy and sell indicators as dicts + extracted_lists = extract_lists(strategy_path) + + buy_indicators = extracted_lists[0] + sell_indicators = extracted_lists[1] + + # use the dicts to write the hyperopt + deploy_custom_hyperopt(args['hyperopt'], new_path, + buy_indicators, sell_indicators) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 1130319b0..4704c31c3 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -279,14 +279,21 @@ AVAILABLE_CLI_OPTIONS = { "buy_indicators": Arg( '-b', '--buy-indicators', help='Specify the buy indicators the hyperopt should build. ' - 'Example: --buy-indicators `{"rsi":"above","bb_lowerband":"below"}`', - metavar='DICT', + 'Example: --buy-indicators `[["rsi","<","trigger",30.0],["bb_lowerband",">","guard","close"]]`' + 'Check the documentation for specific requirements for the lists.', + metavar='LIST', ), "sell_indicators": Arg( '-s', '--sell-indicators', help='Specify the sell indicators the hyperopt should build. ' - 'Example: --sell-indicators `{"rsi":"above","bb_lowerband":"below"}`', - metavar='DICT', + 'Example: --sell-indicators [["rsi",">","trigger",70.0],["bb_lowerband","<","guard","close"]]' + 'Check the documentation for specific requirements for the lists.', + metavar='LIST', + ), + "extract_name": Arg( + '--extract-name', + help='Specify the name of the file to which the data should be extracted. ', + metavar='FILENAME', ), # List exchanges "print_one_column": Arg( diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 802ddc2b1..eefb4c8bb 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -392,4 +392,4 @@ PairWithTimeframe = Tuple[str, str] ListPairsWithTimeframes = List[PairWithTimeframe] # Type for trades list -TradeList = List[List] +TradeList = List[List] \ No newline at end of file diff --git a/freqtrade/templates/base_custom_hyperopt.py.j2 b/freqtrade/templates/base_custom_hyperopt.py.j2 new file mode 100644 index 000000000..672b0b119 --- /dev/null +++ b/freqtrade/templates/base_custom_hyperopt.py.j2 @@ -0,0 +1,164 @@ +# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement + +# --- Do not remove these libs --- +from functools import reduce +from typing import Any, Callable, Dict, List + +import numpy as np # noqa +import pandas as pd # noqa +from pandas import DataFrame +from skopt.space import Categorical, Dimension, Integer, Real # noqa + +from freqtrade.optimize.hyperopt_interface import IHyperOpt + +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta # noqa +import freqtrade.vendor.qtpylib.indicators as qtpylib + + +class {{ hyperopt }}(IHyperOpt): + """ + This is a Hyperopt template to get you started. + + More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ + + You should: + - Add any lib you need to build your hyperopt. + + You must keep: + - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. + + The methods roi_space, generate_roi_table and stoploss_space are not required + and are provided by default. + However, you may override them if you need 'roi' and 'stoploss' spaces that + differ from the defaults offered by Freqtrade. + Sample implementation of these methods will be copied to `user_data/hyperopts` when + creating the user-data directory using `freqtrade create-userdir --userdir user_data`, + or is available online under the following URL: + https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py. + """ + + @staticmethod + def buy_strategy_generator(params: Dict[str, Any]) -> Callable: + """ + Define the buy strategy parameters to be used by Hyperopt. + """ + def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Buy strategy Hyperopt will build and use. + """ + conditions = [] + + # GUARDS AND TRENDS + {{ buy_guards | indent(12) }} + + # TRIGGERS + if 'trigger' in params: + {{ buy_triggers | indent(16) }} + + # Check that the candle had volume + conditions.append(dataframe['volume'] > 0) + + if conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'buy'] = 1 + + return dataframe + + return populate_buy_trend + + @staticmethod + def indicator_space() -> List[Dimension]: + """ + Define your Hyperopt space for searching buy strategy parameters. + """ + return [ + {{ buy_space | indent(12) }} + ] + + @staticmethod + def sell_strategy_generator(params: Dict[str, Any]) -> Callable: + """ + Define the sell strategy parameters to be used by Hyperopt. + """ + def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Sell strategy Hyperopt will build and use. + """ + conditions = [] + + # GUARDS AND TRENDS + {{ sell_guards | indent(12) }} + + # TRIGGERS + if 'sell-trigger' in params: + {{ sell_triggers | indent(16) }} + + # Check that the candle had volume + conditions.append(dataframe['volume'] > 0) + + if conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'sell'] = 1 + + return dataframe + + return populate_sell_trend + + @staticmethod + def sell_indicator_space() -> List[Dimension]: + """ + Define your Hyperopt space for searching sell strategy parameters. + """ + return [ + {{ sell_space | indent(12) }} + ] + @ staticmethod + def generate_roi_table(params: Dict) -> Dict[int, float]: + """ + Generate the ROI table that will be used by Hyperopt + This implementation generates the default legacy Freqtrade ROI tables. + Change it if you need different number of steps in the generated + ROI tables or other structure of the ROI tables. + Please keep it aligned with parameters in the 'roi' optimization + hyperspace defined by the roi_space method. + """ + roi_table = {} + roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3'] + roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2'] + roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1'] + roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0 + + return roi_table + + @ staticmethod + def roi_space() -> List[Dimension]: + """ + Values to search for each ROI steps + Override it if you need some different ranges for the parameters in the + 'roi' optimization hyperspace. + Please keep it aligned with the implementation of the + generate_roi_table method. + """ + return [ + Integer(10, 120, name='roi_t1'), + Integer(10, 60, name='roi_t2'), + Integer(10, 40, name='roi_t3'), + Real(0.01, 0.04, name='roi_p1'), + Real(0.01, 0.07, name='roi_p2'), + Real(0.01, 0.20, name='roi_p3'), + ] + + @ staticmethod + def stoploss_space() -> List[Dimension]: + """ + Stoploss Value to search + Override it if you need some different range for the parameter in the + 'stoploss' optimization hyperspace. + """ + return [ + Real(-0.35, -0.02, name='stoploss'), + ] diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index 38e2f4172..2bdfdba16 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -55,7 +55,16 @@ class {{ hyperopt }}(IHyperOpt): # TRIGGERS if 'trigger' in params: - {{ buy_triggers | indent(16) }} + if params['trigger'] == 'bb_lower': + conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + if params['trigger'] == 'macd_cross_signal': + conditions.append(qtpylib.crossed_above( + dataframe['macd'], dataframe['macdsignal'] + )) + if params['trigger'] == 'sar_reversal': + conditions.append(qtpylib.crossed_above( + dataframe['close'], dataframe['sar'] + )) # Check that the candle had volume conditions.append(dataframe['volume'] > 0) @@ -94,7 +103,16 @@ class {{ hyperopt }}(IHyperOpt): # TRIGGERS if 'sell-trigger' in params: - {{ sell_triggers | indent(16) }} + if params['sell-trigger'] == 'sell-bb_upper': + conditions.append(dataframe['close'] > dataframe['bb_upperband']) + if params['sell-trigger'] == 'sell-macd_cross_signal': + conditions.append(qtpylib.crossed_above( + dataframe['macdsignal'], dataframe['macd'] + )) + if params['sell-trigger'] == 'sell-sar_reversal': + conditions.append(qtpylib.crossed_above( + dataframe['sar'], dataframe['close'] + )) # Check that the candle had volume conditions.append(dataframe['volume'] > 0) @@ -115,4 +133,4 @@ class {{ hyperopt }}(IHyperOpt): """ return [ {{ sell_space | indent(12) }} - ] + ] \ No newline at end of file