diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 2229ea295..0ce9b577b 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -13,7 +13,6 @@ from freqtrade.commands.data_commands import (start_convert_data, start_download 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.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 feebfe279..2393a39b0 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -212,7 +212,7 @@ class Arguments: # add build-hyperopt subcommand build_custom_hyperopt_cmd = subparsers.add_parser('build-hyperopt', - help="Build a custom hyperopt") + help="Build a custom hyperopt") build_custom_hyperopt_cmd.set_defaults(func=start_build_hyperopt) self._build_args(optionlist=ARGS_BUILD_CUSTOM_HYPEROPT, parser=build_custom_hyperopt_cmd) diff --git a/freqtrade/commands/automation_commands.py b/freqtrade/commands/automation_commands.py index 098baad32..a639a8fa2 100644 --- a/freqtrade/commands/automation_commands.py +++ b/freqtrade/commands/automation_commands.py @@ -1,3 +1,4 @@ +import ast import logging from pathlib import Path from typing import Any, Dict @@ -6,40 +7,141 @@ from freqtrade.constants import USERPATH_HYPEROPTS from freqtrade.exceptions import OperationalException from freqtrade.state import RunMode from freqtrade.configuration import setup_utils_configuration -from freqtrade.misc import render_template, render_template_with_fallback +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 +''' -def deploy_custom_hyperopt(hyperopt_name: str, hyperopt_path: Path, buy_indicators: str, sell_indicators: str) -> None: +POSSIBLE_GUARDS = ["rsi", "mfi", "fastd"] +POSSIBLE_TRIGGERS = ["bb_lowerband", "bb_upperband"] +POSSIBLE_VALUES = {"above": ">", "below": "<"} + + +def build_hyperopt_buyelements(buy_indicators: Dict[str, str]): + """ + Build the arguments with the placefillers for the buygenerator + """ + buy_guards = "" + 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}.") + # If the indicator is a guard + elif indicator in POSSIBLE_GUARDS: + # get the symbol corrosponding to the value + aim = POSSIBLE_VALUES[buy_indicators[indicator]] + + # 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'])" + + # 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]] + + # add the trigger to its argument + buy_triggers += f"if params['trigger'] == '{indicator}': conditions.append(dataframe['{indicator}'] {aim} dataframe['close'])" + + # 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: + buy_space += f"'{indicator}', " + + # Deleting the last ", " + buy_space = buy_space[:-2] + buy_space += "], name='trigger')" + + return {"buy_guards": buy_guards, "buy_triggers": buy_triggers, "buy_space": buy_space} + + +def build_hyperopt_sellelements(sell_indicators: Dict[str, str]): + """ + Build the arguments with the placefillers for the sellgenerator + """ + sell_guards = "" + 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]] + + # 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'])" + + # add the space to its argument + sell_space += f"Integer(10, 90, name='sell-{indicator}-value'), Categorical([True, False], name='sell-{indicator}-enabled')," + # If the indicator is a trigger + elif indicator in POSSIBLE_TRIGGERS: + # get the symbol corrosponding to the value + aim = POSSIBLE_VALUES[sell_indicators[indicator]] + + # add the trigger to its argument + sell_triggers += f"if params['sell-trigger'] == 'sell-{indicator}': conditions.append(dataframe['{indicator}'] {aim} dataframe['close'])" + + # 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: + sell_space += f"'sell-{indicator}', " + + # Deleting the last ", " + sell_space = sell_space[:-2] + sell_space += "], name='trigger')" + + return {"sell_guards": sell_guards, "sell_triggers": sell_triggers, "sell_space": sell_space} + + +def deploy_custom_hyperopt(hyperopt_name: str, hyperopt_path: Path, buy_indicators: Dict[str, str], sell_indicators: Dict[str, str]) -> None: """ Deploys a custom hyperopt template to hyperopt_path - TODO make the code below more dynamic with a large list of indicators instead of a few templates """ - fallback = 'full' - buy_guards = render_template_with_fallback( - templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2", - templatefallbackfile=f"subtemplates/hyperopt_buy_guards_{fallback}.j2", - ) - sell_guards = render_template_with_fallback( - templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2", - templatefallbackfile=f"subtemplates/hyperopt_sell_guards_{fallback}.j2", - ) - buy_space = render_template_with_fallback( - templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2", - templatefallbackfile=f"subtemplates/hyperopt_buy_space_{fallback}.j2", - ) - sell_space = render_template_with_fallback( - templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2", - templatefallbackfile=f"subtemplates/hyperopt_sell_space_{fallback}.j2", - ) + # Build the arguments for the buy and sell generators + buy_args = build_hyperopt_buyelements(buy_indicators) + sell_args = build_hyperopt_sellelements(sell_indicators) + + # Build the final template strategy_text = render_template(templatefile='base_hyperopt.py.j2', arguments={"hyperopt": hyperopt_name, - "buy_guards": buy_guards, - "sell_guards": sell_guards, - "buy_space": buy_space, - "sell_space": sell_space, + "buy_guards": buy_args["buy_guards"], + "buy_triggers": buy_args["buy_triggers"], + "buy_space": buy_args["buy_space"], + "sell_guards": sell_args["sell_guards"], + "sell_triggers": sell_args["sell_triggers"], + "sell_space": sell_args["sell_space"], }) logger.info(f"Writing custom hyperopt to `{hyperopt_path}`.") @@ -67,5 +169,9 @@ def start_build_hyperopt(args: Dict[str, Any]) -> None: if new_path.exists(): raise OperationalException(f"`{new_path}` already exists. " "Please choose another Hyperopt Name.") + + buy_indicators = ast.literal_eval(args['buy_indicators']) + sell_indicators = ast.literal_eval(args['sell_indicators']) + deploy_custom_hyperopt(args['hyperopt'], new_path, - args['buy_indicators'], args['sell_indicators']) + buy_indicators, sell_indicators) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 2aba5c606..1130319b0 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -275,6 +275,19 @@ AVAILABLE_CLI_OPTIONS = { 'Example: `--hyperopt-filename=hyperopt_results_2020-09-27_16-20-48.pickle`', metavar='FILENAME', ), + # Automation + "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', + ), + "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', + ), # List exchanges "print_one_column": Arg( '-1', '--one-column', @@ -439,15 +452,6 @@ AVAILABLE_CLI_OPTIONS = { help='Specify the list of trade ids.', nargs='+', ), - # automation - "buy_indicators": Arg( - '-b', '--buy-indicators', - help='Specify the buy indicators the hyperopt should build.' - ), - "sell_indicators": Arg( - '-s', '--sell-indicators', - help='Specify the buy indicators the hyperopt should build.' - ), # hyperopt-list, hyperopt-show "hyperopt_list_profitable": Arg( '--profitable', diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index ec787cbb6..38e2f4172 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -55,16 +55,7 @@ class {{ hyperopt }}(IHyperOpt): # TRIGGERS if 'trigger' in params: - 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'] - )) + {{ buy_triggers | indent(16) }} # Check that the candle had volume conditions.append(dataframe['volume'] > 0) @@ -103,16 +94,7 @@ class {{ hyperopt }}(IHyperOpt): # TRIGGERS if 'sell-trigger' in params: - 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'] - )) + {{ sell_triggers | indent(16) }} # Check that the candle had volume conditions.append(dataframe['volume'] > 0)