added effort as new argument

This commit is contained in:
orehunt 2020-02-24 13:31:46 +01:00
parent 0a49dcb712
commit d96e842a21
5 changed files with 660 additions and 330 deletions

View File

@ -15,18 +15,17 @@ ARGS_STRATEGY = ["strategy", "strategy_path"]
ARGS_TRADE = ["db_url", "sd_notify", "dry_run"] ARGS_TRADE = ["db_url", "sd_notify", "dry_run"]
ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", "max_open_trades", "stake_amount", "fee"]
"max_open_trades", "stake_amount", "fee"]
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + [
"strategy_list", "export", "exportfilename"] "position_stacking", "use_max_market_positions", "strategy_list", "export", "exportfilename"
]
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + [
"position_stacking", "epochs", "spaces", "hyperopt", "hyperopt_path", "position_stacking", "epochs", "spaces",
"use_max_market_positions", "print_all", "use_max_market_positions", "print_all", "print_colorized", "print_json", "hyperopt_jobs",
"print_colorized", "print_json", "hyperopt_jobs", "hyperopt_random_state", "hyperopt_min_trades", "hyperopt_continue", "hyperopt_loss", "effort"
"hyperopt_random_state", "hyperopt_min_trades", ]
"hyperopt_continue", "hyperopt_loss"]
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
@ -38,8 +37,10 @@ ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"] ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"]
ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column", ARGS_LIST_PAIRS = [
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] "exchange", "print_list", "list_pairs_print_json", "print_one_column", "print_csv",
"base_currencies", "quote_currencies", "list_pairs_all"
]
ARGS_TEST_PAIRLIST = ["config", "quote_currencies", "print_one_column", "list_pairs_print_json"] ARGS_TEST_PAIRLIST = ["config", "quote_currencies", "print_one_column", "list_pairs_print_json"]
@ -54,30 +55,38 @@ ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"] ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"] ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange", ARGS_DOWNLOAD_DATA = [
"timeframes", "erase", "dataformat_ohlcv", "dataformat_trades"] "pairs", "pairs_file", "days", "download_trades", "exchange", "timeframes", "erase",
"dataformat_ohlcv", "dataformat_trades"
]
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", ARGS_PLOT_DATAFRAME = [
"db_url", "trade_source", "export", "exportfilename", "pairs", "indicators1", "indicators2", "plot_limit", "db_url", "trade_source", "export",
"timerange", "ticker_interval"] "exportfilename", "timerange", "ticker_interval"
]
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", ARGS_PLOT_PROFIT = [
"trade_source", "ticker_interval"] "pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"
]
ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", ARGS_HYPEROPT_LIST = [
"hyperopt_list_min_trades", "hyperopt_list_max_trades", "hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_trades",
"hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_max_trades", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time",
"hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit",
"hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", "print_colorized",
"print_colorized", "print_json", "hyperopt_list_no_details"] "print_json", "hyperopt_list_no_details"
]
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", ARGS_HYPEROPT_SHOW = [
"print_json", "hyperopt_show_no_header"] "hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json",
"hyperopt_show_no_header"
]
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", NO_CONF_REQURIED = [
"list-markets", "list-pairs", "list-strategies", "convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets",
"list-hyperopts", "hyperopt-list", "hyperopt-show", "list-pairs", "list-strategies", "list-hyperopts", "hyperopt-list", "hyperopt-show",
"plot-dataframe", "plot-profit"] "plot-dataframe", "plot-profit"
]
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"] NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"]
@ -86,7 +95,6 @@ class Arguments:
""" """
Arguments Class. Manage the arguments received by the cli Arguments Class. Manage the arguments received by the cli
""" """
def __init__(self, args: Optional[List[str]]) -> None: def __init__(self, args: Optional[List[str]]) -> None:
self.args = args self.args = args
self._parsed_arg: Optional[argparse.Namespace] = None self._parsed_arg: Optional[argparse.Namespace] = None
@ -155,70 +163,70 @@ class Arguments:
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
self._build_args(optionlist=['version'], parser=self.parser) self._build_args(optionlist=['version'], parser=self.parser)
from freqtrade.commands import (start_create_userdir, start_convert_data, from freqtrade.commands import (
start_download_data, start_create_userdir, start_convert_data, start_download_data, start_hyperopt_list,
start_hyperopt_list, start_hyperopt_show, start_hyperopt_show, start_list_exchanges, start_list_hyperopts, start_list_markets,
start_list_exchanges, start_list_hyperopts, start_list_strategies, start_list_timeframes, start_new_config, start_new_hyperopt,
start_list_markets, start_list_strategies, start_new_strategy, start_plot_dataframe, start_plot_profit, start_backtesting,
start_list_timeframes, start_new_config, start_hyperopt, start_edge, start_test_pairlist, start_trading)
start_new_hyperopt, start_new_strategy,
start_plot_dataframe, start_plot_profit,
start_backtesting, start_hyperopt, start_edge,
start_test_pairlist, start_trading)
subparsers = self.parser.add_subparsers(dest='command', subparsers = self.parser.add_subparsers(
# Use custom message when no subhandler is added dest='command',
# shown from `main.py` # Use custom message when no subhandler is added
# required=True # shown from `main.py`
) # required=True
)
# Add trade subcommand # Add trade subcommand
trade_cmd = subparsers.add_parser('trade', help='Trade module.', trade_cmd = subparsers.add_parser('trade',
help='Trade module.',
parents=[_common_parser, _strategy_parser]) parents=[_common_parser, _strategy_parser])
trade_cmd.set_defaults(func=start_trading) trade_cmd.set_defaults(func=start_trading)
self._build_args(optionlist=ARGS_TRADE, parser=trade_cmd) self._build_args(optionlist=ARGS_TRADE, parser=trade_cmd)
# Add backtesting subcommand # Add backtesting subcommand
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.', backtesting_cmd = subparsers.add_parser('backtesting',
help='Backtesting module.',
parents=[_common_parser, _strategy_parser]) parents=[_common_parser, _strategy_parser])
backtesting_cmd.set_defaults(func=start_backtesting) backtesting_cmd.set_defaults(func=start_backtesting)
self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd)
# Add edge subcommand # Add edge subcommand
edge_cmd = subparsers.add_parser('edge', help='Edge module.', edge_cmd = subparsers.add_parser('edge',
help='Edge module.',
parents=[_common_parser, _strategy_parser]) parents=[_common_parser, _strategy_parser])
edge_cmd.set_defaults(func=start_edge) edge_cmd.set_defaults(func=start_edge)
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd) self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
# Add hyperopt subcommand # Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.', hyperopt_cmd = subparsers.add_parser(
parents=[_common_parser, _strategy_parser], 'hyperopt',
) help='Hyperopt module.',
parents=[_common_parser, _strategy_parser],
)
hyperopt_cmd.set_defaults(func=start_hyperopt) hyperopt_cmd.set_defaults(func=start_hyperopt)
self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd) self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd)
# add create-userdir subcommand # add create-userdir subcommand
create_userdir_cmd = subparsers.add_parser('create-userdir', create_userdir_cmd = subparsers.add_parser(
help="Create user-data directory.", 'create-userdir',
) help="Create user-data directory.",
)
create_userdir_cmd.set_defaults(func=start_create_userdir) create_userdir_cmd.set_defaults(func=start_create_userdir)
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd) self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
# add new-config subcommand # add new-config subcommand
build_config_cmd = subparsers.add_parser('new-config', build_config_cmd = subparsers.add_parser('new-config', help="Create new config")
help="Create new config")
build_config_cmd.set_defaults(func=start_new_config) build_config_cmd.set_defaults(func=start_new_config)
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd) self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
# add new-strategy subcommand # add new-strategy subcommand
build_strategy_cmd = subparsers.add_parser('new-strategy', build_strategy_cmd = subparsers.add_parser('new-strategy', help="Create new strategy")
help="Create new strategy")
build_strategy_cmd.set_defaults(func=start_new_strategy) build_strategy_cmd.set_defaults(func=start_new_strategy)
self._build_args(optionlist=ARGS_BUILD_STRATEGY, parser=build_strategy_cmd) self._build_args(optionlist=ARGS_BUILD_STRATEGY, parser=build_strategy_cmd)
# add new-hyperopt subcommand # add new-hyperopt subcommand
build_hyperopt_cmd = subparsers.add_parser('new-hyperopt', build_hyperopt_cmd = subparsers.add_parser('new-hyperopt', help="Create new hyperopt")
help="Create new hyperopt")
build_hyperopt_cmd.set_defaults(func=start_new_hyperopt) build_hyperopt_cmd.set_defaults(func=start_new_hyperopt)
self._build_args(optionlist=ARGS_BUILD_HYPEROPT, parser=build_hyperopt_cmd) self._build_args(optionlist=ARGS_BUILD_HYPEROPT, parser=build_hyperopt_cmd)

View File

@ -13,8 +13,7 @@ def check_int_positive(value: str) -> int:
raise ValueError raise ValueError
except ValueError: except ValueError:
raise ArgumentTypeError( raise ArgumentTypeError(
f"{value} is invalid for this parameter, should be a positive integer value" f"{value} is invalid for this parameter, should be a positive integer value")
)
return uint return uint
@ -25,8 +24,7 @@ def check_int_nonzero(value: str) -> int:
raise ValueError raise ValueError
except ValueError: except ValueError:
raise ArgumentTypeError( raise ArgumentTypeError(
f"{value} is invalid for this parameter, should be a non-zero integer value" f"{value} is invalid for this parameter, should be a non-zero integer value")
)
return uint return uint
@ -40,25 +38,32 @@ class Arg:
# List of available command line options # List of available command line options
AVAILABLE_CLI_OPTIONS = { AVAILABLE_CLI_OPTIONS = {
# Common options # Common options
"verbosity": Arg( "verbosity":
'-v', '--verbose', Arg(
'-v',
'--verbose',
help='Verbose mode (-vv for more, -vvv to get all messages).', help='Verbose mode (-vv for more, -vvv to get all messages).',
action='count', action='count',
default=0, default=0,
), ),
"logfile": Arg( "logfile":
Arg(
'--logfile', '--logfile',
help="Log to the file specified. Special values are: 'syslog', 'journald'. " help="Log to the file specified. Special values are: 'syslog', 'journald'. "
"See the documentation for more details.", "See the documentation for more details.",
metavar='FILE', metavar='FILE',
), ),
"version": Arg( "version":
'-V', '--version', Arg(
'-V',
'--version',
action='version', action='version',
version=f'%(prog)s {__version__}', version=f'%(prog)s {__version__}',
), ),
"config": Arg( "config":
'-c', '--config', Arg(
'-c',
'--config',
help=f'Specify configuration file (default: `userdir/{constants.DEFAULT_CONFIG}` ' help=f'Specify configuration file (default: `userdir/{constants.DEFAULT_CONFIG}` '
f'or `config.json` whichever exists). ' f'or `config.json` whichever exists). '
f'Multiple --config options may be used. ' f'Multiple --config options may be used. '
@ -66,84 +71,105 @@ AVAILABLE_CLI_OPTIONS = {
action='append', action='append',
metavar='PATH', metavar='PATH',
), ),
"datadir": Arg( "datadir":
'-d', '--datadir', Arg(
'-d',
'--datadir',
help='Path to directory with historical backtesting data.', help='Path to directory with historical backtesting data.',
metavar='PATH', metavar='PATH',
), ),
"user_data_dir": Arg( "user_data_dir":
'--userdir', '--user-data-dir', Arg(
'--userdir',
'--user-data-dir',
help='Path to userdata directory.', help='Path to userdata directory.',
metavar='PATH', metavar='PATH',
), ),
"reset": Arg( "reset":
Arg(
'--reset', '--reset',
help='Reset sample files to their original state.', help='Reset sample files to their original state.',
action='store_true', action='store_true',
), ),
# Main options # Main options
"strategy": Arg( "strategy":
'-s', '--strategy', Arg(
'-s',
'--strategy',
help='Specify strategy class name which will be used by the bot.', help='Specify strategy class name which will be used by the bot.',
metavar='NAME', metavar='NAME',
), ),
"strategy_path": Arg( "strategy_path":
Arg(
'--strategy-path', '--strategy-path',
help='Specify additional strategy lookup path.', help='Specify additional strategy lookup path.',
metavar='PATH', metavar='PATH',
), ),
"db_url": Arg( "db_url":
Arg(
'--db-url', '--db-url',
help=f'Override trades database URL, this is useful in custom deployments ' help=f'Override trades database URL, this is useful in custom deployments '
f'(default: `{constants.DEFAULT_DB_PROD_URL}` for Live Run mode, ' f'(default: `{constants.DEFAULT_DB_PROD_URL}` for Live Run mode, '
f'`{constants.DEFAULT_DB_DRYRUN_URL}` for Dry Run).', f'`{constants.DEFAULT_DB_DRYRUN_URL}` for Dry Run).',
metavar='PATH', metavar='PATH',
), ),
"sd_notify": Arg( "sd_notify":
Arg(
'--sd-notify', '--sd-notify',
help='Notify systemd service manager.', help='Notify systemd service manager.',
action='store_true', action='store_true',
), ),
"dry_run": Arg( "dry_run":
Arg(
'--dry-run', '--dry-run',
help='Enforce dry-run for trading (removes Exchange secrets and simulates trades).', help='Enforce dry-run for trading (removes Exchange secrets and simulates trades).',
action='store_true', action='store_true',
), ),
# Optimize common # Optimize common
"ticker_interval": Arg( "ticker_interval":
'-i', '--ticker-interval', Arg(
'-i',
'--ticker-interval',
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).', help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
), ),
"timerange": Arg( "timerange":
Arg(
'--timerange', '--timerange',
help='Specify what timerange of data to use.', help='Specify what timerange of data to use.',
), ),
"max_open_trades": Arg( "max_open_trades":
Arg(
'--max-open-trades', '--max-open-trades',
help='Override the value of the `max_open_trades` configuration setting.', help='Override the value of the `max_open_trades` configuration setting.',
type=int, type=int,
metavar='INT', metavar='INT',
), ),
"stake_amount": Arg( "stake_amount":
Arg(
'--stake-amount', '--stake-amount',
help='Override the value of the `stake_amount` configuration setting.', help='Override the value of the `stake_amount` configuration setting.',
type=float, type=float,
), ),
# Backtesting # Backtesting
"position_stacking": Arg( "position_stacking":
'--eps', '--enable-position-stacking', Arg(
'--eps',
'--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking).', help='Allow buying the same pair multiple times (position stacking).',
action='store_true', action='store_true',
default=False, default=False,
), ),
"use_max_market_positions": Arg( "use_max_market_positions":
'--dmmp', '--disable-max-market-positions', Arg(
'--dmmp',
'--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest ' help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number).', '(same as setting `max_open_trades` to a very high number).',
action='store_false', action='store_false',
default=True, default=True,
), ),
"strategy_list": Arg( "strategy_list":
Arg(
'--strategy-list', '--strategy-list',
help='Provide a space-separated list of strategies to backtest. ' help='Provide a space-separated list of strategies to backtest. '
'Please note that ticker-interval needs to be set either in config ' 'Please note that ticker-interval needs to be set either in config '
@ -152,77 +178,100 @@ AVAILABLE_CLI_OPTIONS = {
'(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`', '(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`',
nargs='+', nargs='+',
), ),
"export": Arg( "export":
Arg(
'--export', '--export',
help='Export backtest results, argument are: trades. ' help='Export backtest results, argument are: trades. '
'Example: `--export=trades`', 'Example: `--export=trades`',
), ),
"exportfilename": Arg( "exportfilename":
Arg(
'--export-filename', '--export-filename',
help='Save backtest results to the file with this filename. ' help='Save backtest results to the file with this filename. '
'Requires `--export` to be set as well. ' 'Requires `--export` to be set as well. '
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`', 'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
metavar='PATH', metavar='PATH',
), ),
"fee": Arg( "fee":
Arg(
'--fee', '--fee',
help='Specify fee ratio. Will be applied twice (on trade entry and exit).', help='Specify fee ratio. Will be applied twice (on trade entry and exit).',
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
# Edge # Edge
"stoploss_range": Arg( "stoploss_range":
Arg(
'--stoplosses', '--stoplosses',
help='Defines a range of stoploss values against which edge will assess the strategy. ' help='Defines a range of stoploss values against which edge will assess the strategy. '
'The format is "min,max,step" (without any space). ' 'The format is "min,max,step" (without any space). '
'Example: `--stoplosses=-0.01,-0.1,-0.001`', 'Example: `--stoplosses=-0.01,-0.1,-0.001`',
), ),
# Hyperopt # Hyperopt
"hyperopt": Arg( "hyperopt":
Arg(
'--hyperopt', '--hyperopt',
help='Specify hyperopt class name which will be used by the bot.', help='Specify hyperopt class name which will be used by the bot.',
metavar='NAME', metavar='NAME',
), ),
"hyperopt_path": Arg( "hyperopt_path":
Arg(
'--hyperopt-path', '--hyperopt-path',
help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.', help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.',
metavar='PATH', metavar='PATH',
), ),
"epochs": Arg( "epochs":
'-e', '--epochs', Arg(
'-e',
'--epochs',
help='Specify number of epochs (default: %(default)d).', help='Specify number of epochs (default: %(default)d).',
type=check_int_positive, type=check_int_positive,
metavar='INT', metavar='INT',
default=constants.HYPEROPT_EPOCH, default=constants.HYPEROPT_EPOCH,
), ),
"spaces": Arg( "effort":
Arg(
'--effort',
help=('The higher the number, the longer will be the search if'
'no epochs are defined (default: %(default)d).'),
type=check_int_positive,
metavar='INT',
default=constants.HYPEROPT_EFFORT,
),
"spaces":
Arg(
'--spaces', '--spaces',
help='Specify which parameters to hyperopt. Space-separated list.', help='Specify which parameters to hyperopt. Space-separated list.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'default'], choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'default'],
nargs='+', nargs='+',
default='default', default='default',
), ),
"print_all": Arg( "print_all":
Arg(
'--print-all', '--print-all',
help='Print all results, not only the best ones.', help='Print all results, not only the best ones.',
action='store_true', action='store_true',
default=False, default=False,
), ),
"print_colorized": Arg( "print_colorized":
Arg(
'--no-color', '--no-color',
help='Disable colorization of hyperopt results. May be useful if you are ' help='Disable colorization of hyperopt results. May be useful if you are '
'redirecting output to a file.', 'redirecting output to a file.',
action='store_false', action='store_false',
default=True, default=True,
), ),
"print_json": Arg( "print_json":
Arg(
'--print-json', '--print-json',
help='Print best result detailization in JSON format.', help='Print best result detailization in JSON format.',
action='store_true', action='store_true',
default=False, default=False,
), ),
"hyperopt_jobs": Arg( "hyperopt_jobs":
'-j', '--job-workers', Arg(
'-j',
'--job-workers',
help='The number of concurrently running jobs for hyperoptimization ' help='The number of concurrently running jobs for hyperoptimization '
'(hyperopt worker processes). ' '(hyperopt worker processes). '
'If -1 (default), all CPUs are used, for -2, all CPUs but one are used, etc. ' 'If -1 (default), all CPUs are used, for -2, all CPUs but one are used, etc. '
@ -231,13 +280,15 @@ AVAILABLE_CLI_OPTIONS = {
metavar='JOBS', metavar='JOBS',
default=-1, default=-1,
), ),
"hyperopt_random_state": Arg( "hyperopt_random_state":
Arg(
'--random-state', '--random-state',
help='Set random state to some positive integer for reproducible hyperopt results.', help='Set random state to some positive integer for reproducible hyperopt results.',
type=check_int_positive, type=check_int_positive,
metavar='INT', metavar='INT',
), ),
"hyperopt_min_trades": Arg( "hyperopt_min_trades":
Arg(
'--min-trades', '--min-trades',
help="Set minimal desired number of trades for evaluations in the hyperopt " help="Set minimal desired number of trades for evaluations in the hyperopt "
"optimization path (default: 1).", "optimization path (default: 1).",
@ -245,14 +296,16 @@ AVAILABLE_CLI_OPTIONS = {
metavar='INT', metavar='INT',
default=1, default=1,
), ),
"hyperopt_continue": Arg( "hyperopt_continue":
Arg(
"--continue", "--continue",
help="Continue hyperopt from previous runs. " help="Continue hyperopt from previous runs. "
"By default, temporary files will be removed and hyperopt will start from scratch.", "By default, temporary files will be removed and hyperopt will start from scratch.",
default=False, default=False,
action='store_true', action='store_true',
), ),
"hyperopt_loss": Arg( "hyperopt_loss":
Arg(
'--hyperopt-loss', '--hyperopt-loss',
help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). '
'Different functions can generate completely different results, ' 'Different functions can generate completely different results, '
@ -263,121 +316,143 @@ AVAILABLE_CLI_OPTIONS = {
default=constants.DEFAULT_HYPEROPT_LOSS, default=constants.DEFAULT_HYPEROPT_LOSS,
), ),
# List exchanges # List exchanges
"print_one_column": Arg( "print_one_column":
'-1', '--one-column', Arg(
'-1',
'--one-column',
help='Print output in one column.', help='Print output in one column.',
action='store_true', action='store_true',
), ),
"list_exchanges_all": Arg( "list_exchanges_all":
'-a', '--all', Arg(
'-a',
'--all',
help='Print all exchanges known to the ccxt library.', help='Print all exchanges known to the ccxt library.',
action='store_true', action='store_true',
), ),
# List pairs / markets # List pairs / markets
"list_pairs_all": Arg( "list_pairs_all":
'-a', '--all', Arg(
'-a',
'--all',
help='Print all pairs or market symbols. By default only active ' help='Print all pairs or market symbols. By default only active '
'ones are shown.', 'ones are shown.',
action='store_true', action='store_true',
), ),
"print_list": Arg( "print_list":
Arg(
'--print-list', '--print-list',
help='Print list of pairs or market symbols. By default data is ' help='Print list of pairs or market symbols. By default data is '
'printed in the tabular format.', 'printed in the tabular format.',
action='store_true', action='store_true',
), ),
"list_pairs_print_json": Arg( "list_pairs_print_json":
Arg(
'--print-json', '--print-json',
help='Print list of pairs or market symbols in JSON format.', help='Print list of pairs or market symbols in JSON format.',
action='store_true', action='store_true',
default=False, default=False,
), ),
"print_csv": Arg( "print_csv":
Arg(
'--print-csv', '--print-csv',
help='Print exchange pair or market data in the csv format.', help='Print exchange pair or market data in the csv format.',
action='store_true', action='store_true',
), ),
"quote_currencies": Arg( "quote_currencies":
Arg(
'--quote', '--quote',
help='Specify quote currency(-ies). Space-separated list.', help='Specify quote currency(-ies). Space-separated list.',
nargs='+', nargs='+',
metavar='QUOTE_CURRENCY', metavar='QUOTE_CURRENCY',
), ),
"base_currencies": Arg( "base_currencies":
Arg(
'--base', '--base',
help='Specify base currency(-ies). Space-separated list.', help='Specify base currency(-ies). Space-separated list.',
nargs='+', nargs='+',
metavar='BASE_CURRENCY', metavar='BASE_CURRENCY',
), ),
# Script options # Script options
"pairs": Arg( "pairs":
'-p', '--pairs', Arg(
'-p',
'--pairs',
help='Show profits for only these pairs. Pairs are space-separated.', help='Show profits for only these pairs. Pairs are space-separated.',
nargs='+', nargs='+',
), ),
# Download data # Download data
"pairs_file": Arg( "pairs_file":
Arg(
'--pairs-file', '--pairs-file',
help='File containing a list of pairs to download.', help='File containing a list of pairs to download.',
metavar='FILE', metavar='FILE',
), ),
"days": Arg( "days":
Arg(
'--days', '--days',
help='Download data for given number of days.', help='Download data for given number of days.',
type=check_int_positive, type=check_int_positive,
metavar='INT', metavar='INT',
), ),
"download_trades": Arg( "download_trades":
Arg(
'--dl-trades', '--dl-trades',
help='Download trades instead of OHLCV data. The bot will resample trades to the ' help='Download trades instead of OHLCV data. The bot will resample trades to the '
'desired timeframe as specified as --timeframes/-t.', 'desired timeframe as specified as --timeframes/-t.',
action='store_true', action='store_true',
), ),
"format_from": Arg( "format_from":
Arg(
'--format-from', '--format-from',
help='Source format for data conversion.', help='Source format for data conversion.',
choices=constants.AVAILABLE_DATAHANDLERS, choices=constants.AVAILABLE_DATAHANDLERS,
required=True, required=True,
), ),
"format_to": Arg( "format_to":
Arg(
'--format-to', '--format-to',
help='Destination format for data conversion.', help='Destination format for data conversion.',
choices=constants.AVAILABLE_DATAHANDLERS, choices=constants.AVAILABLE_DATAHANDLERS,
required=True, required=True,
), ),
"dataformat_ohlcv": Arg( "dataformat_ohlcv":
'--data-format-ohlcv', Arg('--data-format-ohlcv',
help='Storage format for downloaded ohlcv data. (default: `%(default)s`).', help='Storage format for downloaded ohlcv data. (default: `%(default)s`).',
choices=constants.AVAILABLE_DATAHANDLERS, choices=constants.AVAILABLE_DATAHANDLERS,
default='json' default='json'),
), "dataformat_trades":
"dataformat_trades": Arg( Arg('--data-format-trades',
'--data-format-trades',
help='Storage format for downloaded trades data. (default: `%(default)s`).', help='Storage format for downloaded trades data. (default: `%(default)s`).',
choices=constants.AVAILABLE_DATAHANDLERS, choices=constants.AVAILABLE_DATAHANDLERS,
default='jsongz' default='jsongz'),
), "exchange":
"exchange": Arg( Arg(
'--exchange', '--exchange',
help=f'Exchange name (default: `{constants.DEFAULT_EXCHANGE}`). ' help=f'Exchange name (default: `{constants.DEFAULT_EXCHANGE}`). '
f'Only valid if no config is provided.', f'Only valid if no config is provided.',
), ),
"timeframes": Arg( "timeframes":
'-t', '--timeframes', Arg(
'-t',
'--timeframes',
help=f'Specify which tickers to download. Space-separated list. ' help=f'Specify which tickers to download. Space-separated list. '
f'Default: `1m 5m`.', f'Default: `1m 5m`.',
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', choices=[
'6h', '8h', '12h', '1d', '3d', '1w'], '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w'
],
default=['1m', '5m'], default=['1m', '5m'],
nargs='+', nargs='+',
), ),
"erase": Arg( "erase":
Arg(
'--erase', '--erase',
help='Clean all existing data for the selected exchange/pairs/timeframes.', help='Clean all existing data for the selected exchange/pairs/timeframes.',
action='store_true', action='store_true',
), ),
# Templating options # Templating options
"template": Arg( "template":
Arg(
'--template', '--template',
help='Use a template which is either `minimal` or ' help='Use a template which is either `minimal` or '
'`full` (containing multiple sample indicators). Default: `%(default)s`.', '`full` (containing multiple sample indicators). Default: `%(default)s`.',
@ -385,19 +460,22 @@ AVAILABLE_CLI_OPTIONS = {
default='full', default='full',
), ),
# Plot dataframe # Plot dataframe
"indicators1": Arg( "indicators1":
Arg(
'--indicators1', '--indicators1',
help='Set indicators from your strategy you want in the first row of the graph. ' help='Set indicators from your strategy you want in the first row of the graph. '
"Space-separated list. Example: `ema3 ema5`. Default: `['sma', 'ema3', 'ema5']`.", "Space-separated list. Example: `ema3 ema5`. Default: `['sma', 'ema3', 'ema5']`.",
nargs='+', nargs='+',
), ),
"indicators2": Arg( "indicators2":
Arg(
'--indicators2', '--indicators2',
help='Set indicators from your strategy you want in the third row of the graph. ' help='Set indicators from your strategy you want in the third row of the graph. '
"Space-separated list. Example: `fastd fastk`. Default: `['macd', 'macdsignal']`.", "Space-separated list. Example: `fastd fastk`. Default: `['macd', 'macdsignal']`.",
nargs='+', nargs='+',
), ),
"plot_limit": Arg( "plot_limit":
Arg(
'--plot-limit', '--plot-limit',
help='Specify tick limit for plotting. Notice: too high values cause huge files. ' help='Specify tick limit for plotting. Notice: too high values cause huge files. '
'Default: %(default)s.', 'Default: %(default)s.',
@ -405,7 +483,8 @@ AVAILABLE_CLI_OPTIONS = {
metavar='INT', metavar='INT',
default=750, default=750,
), ),
"trade_source": Arg( "trade_source":
Arg(
'--trade-source', '--trade-source',
help='Specify the source for trades (Can be DB or file (backtest file)) ' help='Specify the source for trades (Can be DB or file (backtest file)) '
'Default: %(default)s', 'Default: %(default)s',
@ -413,76 +492,90 @@ AVAILABLE_CLI_OPTIONS = {
default="file", default="file",
), ),
# hyperopt-list, hyperopt-show # hyperopt-list, hyperopt-show
"hyperopt_list_profitable": Arg( "hyperopt_list_profitable":
Arg(
'--profitable', '--profitable',
help='Select only profitable epochs.', help='Select only profitable epochs.',
action='store_true', action='store_true',
), ),
"hyperopt_list_best": Arg( "hyperopt_list_best":
Arg(
'--best', '--best',
help='Select only best epochs.', help='Select only best epochs.',
action='store_true', action='store_true',
), ),
"hyperopt_list_min_trades": Arg( "hyperopt_list_min_trades":
Arg(
'--min-trades', '--min-trades',
help='Select epochs with more than INT trades.', help='Select epochs with more than INT trades.',
type=check_int_positive, type=check_int_positive,
metavar='INT', metavar='INT',
), ),
"hyperopt_list_max_trades": Arg( "hyperopt_list_max_trades":
Arg(
'--max-trades', '--max-trades',
help='Select epochs with less than INT trades.', help='Select epochs with less than INT trades.',
type=check_int_positive, type=check_int_positive,
metavar='INT', metavar='INT',
), ),
"hyperopt_list_min_avg_time": Arg( "hyperopt_list_min_avg_time":
Arg(
'--min-avg-time', '--min-avg-time',
help='Select epochs on above average time.', help='Select epochs on above average time.',
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
"hyperopt_list_max_avg_time": Arg( "hyperopt_list_max_avg_time":
Arg(
'--max-avg-time', '--max-avg-time',
help='Select epochs on under average time.', help='Select epochs on under average time.',
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
"hyperopt_list_min_avg_profit": Arg( "hyperopt_list_min_avg_profit":
Arg(
'--min-avg-profit', '--min-avg-profit',
help='Select epochs on above average profit.', help='Select epochs on above average profit.',
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
"hyperopt_list_max_avg_profit": Arg( "hyperopt_list_max_avg_profit":
Arg(
'--max-avg-profit', '--max-avg-profit',
help='Select epochs on below average profit.', help='Select epochs on below average profit.',
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
"hyperopt_list_min_total_profit": Arg( "hyperopt_list_min_total_profit":
Arg(
'--min-total-profit', '--min-total-profit',
help='Select epochs on above total profit.', help='Select epochs on above total profit.',
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
"hyperopt_list_max_total_profit": Arg( "hyperopt_list_max_total_profit":
Arg(
'--max-total-profit', '--max-total-profit',
help='Select epochs on below total profit.', help='Select epochs on below total profit.',
type=float, type=float,
metavar='FLOAT', metavar='FLOAT',
), ),
"hyperopt_list_no_details": Arg( "hyperopt_list_no_details":
Arg(
'--no-details', '--no-details',
help='Do not print best epoch details.', help='Do not print best epoch details.',
action='store_true', action='store_true',
), ),
"hyperopt_show_index": Arg( "hyperopt_show_index":
'-n', '--index', Arg(
'-n',
'--index',
help='Specify the index of the epoch to print details for.', help='Specify the index of the epoch to print details for.',
type=check_int_nonzero, type=check_int_nonzero,
metavar='INT', metavar='INT',
), ),
"hyperopt_show_no_header": Arg( "hyperopt_show_no_header":
Arg(
'--no-header', '--no-header',
help='Do not print epoch details header.', help='Do not print epoch details header.',
action='store_true', action='store_true',

View File

@ -1,12 +1,12 @@
# pragma pylint: disable=too-few-public-methods # pragma pylint: disable=too-few-public-methods
""" """
bot constants bot constants
""" """
DEFAULT_CONFIG = 'config.json' DEFAULT_CONFIG = 'config.json'
DEFAULT_EXCHANGE = 'bittrex' DEFAULT_EXCHANGE = 'bittrex'
PROCESS_THROTTLE_SECS = 5 # sec PROCESS_THROTTLE_SECS = 5 # sec
HYPEROPT_EPOCH = 100 # epochs HYPEROPT_EPOCH = 0 # epochs
HYPEROPT_EFFORT = 0 # /10
RETRY_TIMEOUT = 30 # sec RETRY_TIMEOUT = 30 # sec
DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss' DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss'
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
@ -17,8 +17,9 @@ REQUIRED_ORDERTIF = ['buy', 'sell']
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', AVAILABLE_PAIRLISTS = [
'PrecisionFilter', 'PriceFilter', 'SpreadFilter'] 'StaticPairList', 'VolumePairList', 'PrecisionFilter', 'PriceFilter', 'SpreadFilter'
]
AVAILABLE_DATAHANDLERS = ['json', 'jsongz'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz']
DRY_RUN_WALLET = 1000 DRY_RUN_WALLET = 1000
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
@ -38,11 +39,9 @@ USER_DATA_FILES = {
} }
SUPPORTED_FIAT = [ SUPPORTED_FIAT = [
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR",
"EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK",
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "SGD", "THB", "TRY", "TWD", "ZAR", "USD", "BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT"
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD",
"BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT"
] ]
MINIMAL_CONFIG = { MINIMAL_CONFIG = {
@ -63,9 +62,16 @@ MINIMAL_CONFIG = {
CONF_SCHEMA = { CONF_SCHEMA = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1}, 'max_open_trades': {
'ticker_interval': {'type': 'string'}, 'type': ['integer', 'number'],
'stake_currency': {'type': 'string'}, 'minimum': -1
},
'ticker_interval': {
'type': 'string'
},
'stake_currency': {
'type': 'string'
},
'stake_amount': { 'stake_amount': {
'type': ['number', 'string'], 'type': ['number', 'string'],
'minimum': 0.0001, 'minimum': 0.0001,
@ -77,32 +83,76 @@ CONF_SCHEMA = {
'maximum': 1, 'maximum': 1,
'default': 0.99 'default': 0.99
}, },
'amend_last_stake_amount': {'type': 'boolean', 'default': False}, 'amend_last_stake_amount': {
'last_stake_amount_min_ratio': { 'type': 'boolean',
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5 'default': False
},
'last_stake_amount_min_ratio': {
'type': 'number',
'minimum': 0.0,
'maximum': 1.0,
'default': 0.5
},
'fiat_display_currency': {
'type': 'string',
'enum': SUPPORTED_FIAT
},
'dry_run': {
'type': 'boolean'
},
'dry_run_wallet': {
'type': 'number',
'default': DRY_RUN_WALLET
},
'process_only_new_candles': {
'type': 'boolean'
}, },
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
'process_only_new_candles': {'type': 'boolean'},
'minimal_roi': { 'minimal_roi': {
'type': 'object', 'type': 'object',
'patternProperties': { 'patternProperties': {
'^[0-9.]+$': {'type': 'number'} '^[0-9.]+$': {
'type': 'number'
}
}, },
'minProperties': 1 'minProperties': 1
}, },
'amount_reserve_percent': {'type': 'number', 'minimum': 0.0, 'maximum': 0.5}, 'amount_reserve_percent': {
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'type': 'number',
'trailing_stop': {'type': 'boolean'}, 'minimum': 0.0,
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'maximum': 0.5
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, },
'trailing_only_offset_is_reached': {'type': 'boolean'}, 'stoploss': {
'type': 'number',
'maximum': 0,
'exclusiveMaximum': True
},
'trailing_stop': {
'type': 'boolean'
},
'trailing_stop_positive': {
'type': 'number',
'minimum': 0,
'maximum': 1
},
'trailing_stop_positive_offset': {
'type': 'number',
'minimum': 0,
'maximum': 1
},
'trailing_only_offset_is_reached': {
'type': 'boolean'
},
'unfilledtimeout': { 'unfilledtimeout': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'buy': {'type': 'number', 'minimum': 1}, 'buy': {
'sell': {'type': 'number', 'minimum': 1} 'type': 'number',
'minimum': 1
},
'sell': {
'type': 'number',
'minimum': 1
}
} }
}, },
'bid_strategy': { 'bid_strategy': {
@ -113,13 +163,24 @@ CONF_SCHEMA = {
'minimum': 0, 'minimum': 0,
'maximum': 1, 'maximum': 1,
'exclusiveMaximum': False, 'exclusiveMaximum': False,
'use_order_book': {'type': 'boolean'}, 'use_order_book': {
'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1}, 'type': 'boolean'
},
'order_book_top': {
'type': 'integer',
'maximum': 20,
'minimum': 1
},
'check_depth_of_market': { 'check_depth_of_market': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'enabled': {'type': 'boolean'}, 'enabled': {
'bids_to_ask_delta': {'type': 'number', 'minimum': 0}, 'type': 'boolean'
},
'bids_to_ask_delta': {
'type': 'number',
'minimum': 0
},
} }
}, },
}, },
@ -129,43 +190,92 @@ CONF_SCHEMA = {
'ask_strategy': { 'ask_strategy': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'use_order_book': {'type': 'boolean'}, 'use_order_book': {
'order_book_min': {'type': 'integer', 'minimum': 1}, 'type': 'boolean'
'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50}, },
'use_sell_signal': {'type': 'boolean'}, 'order_book_min': {
'sell_profit_only': {'type': 'boolean'}, 'type': 'integer',
'ignore_roi_if_buy_signal': {'type': 'boolean'} 'minimum': 1
},
'order_book_max': {
'type': 'integer',
'minimum': 1,
'maximum': 50
},
'use_sell_signal': {
'type': 'boolean'
},
'sell_profit_only': {
'type': 'boolean'
},
'ignore_roi_if_buy_signal': {
'type': 'boolean'
}
} }
}, },
'order_types': { 'order_types': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'buy': {
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'type': 'string',
'emergencysell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, 'enum': ORDERTYPE_POSSIBILITIES
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, },
'stoploss_on_exchange': {'type': 'boolean'}, 'sell': {
'stoploss_on_exchange_interval': {'type': 'number'} 'type': 'string',
'enum': ORDERTYPE_POSSIBILITIES
},
'emergencysell': {
'type': 'string',
'enum': ORDERTYPE_POSSIBILITIES
},
'stoploss': {
'type': 'string',
'enum': ORDERTYPE_POSSIBILITIES
},
'stoploss_on_exchange': {
'type': 'boolean'
},
'stoploss_on_exchange_interval': {
'type': 'number'
}
}, },
'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] 'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
}, },
'order_time_in_force': { 'order_time_in_force': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'buy': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES}, 'buy': {
'sell': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES} 'type': 'string',
'enum': ORDERTIF_POSSIBILITIES
},
'sell': {
'type': 'string',
'enum': ORDERTIF_POSSIBILITIES
}
}, },
'required': ['buy', 'sell'] 'required': ['buy', 'sell']
}, },
'exchange': {'$ref': '#/definitions/exchange'}, 'exchange': {
'edge': {'$ref': '#/definitions/edge'}, '$ref': '#/definitions/exchange'
},
'edge': {
'$ref': '#/definitions/edge'
},
'experimental': { 'experimental': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'use_sell_signal': {'type': 'boolean'}, 'use_sell_signal': {
'sell_profit_only': {'type': 'boolean'}, 'type': 'boolean'
'ignore_roi_if_buy_signal': {'type': 'boolean'}, },
'block_bad_exchanges': {'type': 'boolean'} 'sell_profit_only': {
'type': 'boolean'
},
'ignore_roi_if_buy_signal': {
'type': 'boolean'
},
'block_bad_exchanges': {
'type': 'boolean'
}
} }
}, },
'pairlists': { 'pairlists': {
@ -173,8 +283,13 @@ CONF_SCHEMA = {
'items': { 'items': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, 'method': {
'config': {'type': 'object'} 'type': 'string',
'enum': AVAILABLE_PAIRLISTS
},
'config': {
'type': 'object'
}
}, },
'required': ['method'], 'required': ['method'],
} }
@ -182,71 +297,126 @@ CONF_SCHEMA = {
'telegram': { 'telegram': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'enabled': {'type': 'boolean'}, 'enabled': {
'token': {'type': 'string'}, 'type': 'boolean'
'chat_id': {'type': 'string'}, },
'token': {
'type': 'string'
},
'chat_id': {
'type': 'string'
},
}, },
'required': ['enabled', 'token', 'chat_id'] 'required': ['enabled', 'token', 'chat_id']
}, },
'webhook': { 'webhook': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'enabled': {'type': 'boolean'}, 'enabled': {
'webhookbuy': {'type': 'object'}, 'type': 'boolean'
'webhookbuycancel': {'type': 'object'}, },
'webhooksell': {'type': 'object'}, 'webhookbuy': {
'webhooksellcancel': {'type': 'object'}, 'type': 'object'
'webhookstatus': {'type': 'object'}, },
'webhookbuycancel': {
'type': 'object'
},
'webhooksell': {
'type': 'object'
},
'webhooksellcancel': {
'type': 'object'
},
'webhookstatus': {
'type': 'object'
},
}, },
}, },
'api_server': { 'api_server': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'enabled': {'type': 'boolean'}, 'enabled': {
'listen_ip_address': {'format': 'ipv4'}, 'type': 'boolean'
},
'listen_ip_address': {
'format': 'ipv4'
},
'listen_port': { 'listen_port': {
'type': 'integer', 'type': 'integer',
'minimum': 1024, 'minimum': 1024,
'maximum': 65535 'maximum': 65535
}, },
'username': {'type': 'string'}, 'username': {
'password': {'type': 'string'}, 'type': 'string'
},
'password': {
'type': 'string'
},
}, },
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password'] 'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
}, },
'db_url': {'type': 'string'}, 'db_url': {
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']}, 'type': 'string'
'forcebuy_enable': {'type': 'boolean'}, },
'initial_state': {
'type': 'string',
'enum': ['running', 'stopped']
},
'forcebuy_enable': {
'type': 'boolean'
},
'internals': { 'internals': {
'type': 'object', 'type': 'object',
'default': {}, 'default': {},
'properties': { 'properties': {
'process_throttle_secs': {'type': 'integer'}, 'process_throttle_secs': {
'interval': {'type': 'integer'}, 'type': 'integer'
'sd_notify': {'type': 'boolean'}, },
'interval': {
'type': 'integer'
},
'sd_notify': {
'type': 'boolean'
},
} }
}, },
'dataformat_ohlcv': { 'dataformat_ohlcv': {
'type': 'string', 'type': 'string',
'enum': AVAILABLE_DATAHANDLERS, 'enum': AVAILABLE_DATAHANDLERS,
'default': 'json' 'default': 'json'
}, },
'dataformat_trades': { 'dataformat_trades': {
'type': 'string', 'type': 'string',
'enum': AVAILABLE_DATAHANDLERS, 'enum': AVAILABLE_DATAHANDLERS,
'default': 'jsongz' 'default': 'jsongz'
} }
}, },
'definitions': { 'definitions': {
'exchange': { 'exchange': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'name': {'type': 'string'}, 'name': {
'sandbox': {'type': 'boolean', 'default': False}, 'type': 'string'
'key': {'type': 'string', 'default': ''}, },
'secret': {'type': 'string', 'default': ''}, 'sandbox': {
'password': {'type': 'string', 'default': ''}, 'type': 'boolean',
'uid': {'type': 'string'}, 'default': False
},
'key': {
'type': 'string',
'default': ''
},
'secret': {
'type': 'string',
'default': ''
},
'password': {
'type': 'string',
'default': ''
},
'uid': {
'type': 'string'
},
'pair_whitelist': { 'pair_whitelist': {
'type': 'array', 'type': 'array',
'items': { 'items': {
@ -263,29 +433,65 @@ CONF_SCHEMA = {
}, },
'uniqueItems': True 'uniqueItems': True
}, },
'outdated_offset': {'type': 'integer', 'minimum': 1}, 'outdated_offset': {
'markets_refresh_interval': {'type': 'integer'}, 'type': 'integer',
'ccxt_config': {'type': 'object'}, 'minimum': 1
'ccxt_async_config': {'type': 'object'} },
'markets_refresh_interval': {
'type': 'integer'
},
'ccxt_config': {
'type': 'object'
},
'ccxt_async_config': {
'type': 'object'
}
}, },
'required': ['name'] 'required': ['name']
}, },
'edge': { 'edge': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'enabled': {'type': 'boolean'}, 'enabled': {
'process_throttle_secs': {'type': 'integer', 'minimum': 600}, 'type': 'boolean'
'calculate_since_number_of_days': {'type': 'integer'}, },
'allowed_risk': {'type': 'number'}, 'process_throttle_secs': {
'capital_available_percentage': {'type': 'number'}, 'type': 'integer',
'stoploss_range_min': {'type': 'number'}, 'minimum': 600
'stoploss_range_max': {'type': 'number'}, },
'stoploss_range_step': {'type': 'number'}, 'calculate_since_number_of_days': {
'minimum_winrate': {'type': 'number'}, 'type': 'integer'
'minimum_expectancy': {'type': 'number'}, },
'min_trade_number': {'type': 'number'}, 'allowed_risk': {
'max_trade_duration_minute': {'type': 'integer'}, 'type': 'number'
'remove_pumps': {'type': 'boolean'} },
'capital_available_percentage': {
'type': 'number'
},
'stoploss_range_min': {
'type': 'number'
},
'stoploss_range_max': {
'type': 'number'
},
'stoploss_range_step': {
'type': 'number'
},
'minimum_winrate': {
'type': 'number'
},
'minimum_expectancy': {
'type': 'number'
},
'min_trade_number': {
'type': 'number'
},
'max_trade_duration_minute': {
'type': 'integer'
},
'remove_pumps': {
'type': 'boolean'
}
}, },
'required': ['process_throttle_secs', 'allowed_risk'] 'required': ['process_throttle_secs', 'allowed_risk']
} }

View File

@ -2,7 +2,6 @@
""" """
This module contains the hyperopt logic This module contains the hyperopt logic
""" """
import os import os
import functools import functools
import locale import locale
@ -10,12 +9,12 @@ import logging
import random import random
import sys import sys
import warnings import warnings
from collections import OrderedDict from collections import OrderedDict, deque
from math import factorial, log from math import factorial, log
from operator import itemgetter from operator import itemgetter
from pathlib import Path from pathlib import Path
from pprint import pprint from pprint import pprint
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional, Callable
import rapidjson import rapidjson
from colorama import Fore, Style from colorama import Fore, Style
@ -32,7 +31,6 @@ from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F401
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F401 from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F401
from freqtrade.resolvers.hyperopt_resolver import (HyperOptLossResolver, HyperOptResolver) from freqtrade.resolvers.hyperopt_resolver import (HyperOptLossResolver, HyperOptResolver)
from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects) from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects)
from joblib._parallel_backends import LokyBackend
from joblib import register_parallel_backend, parallel_backend from joblib import register_parallel_backend, parallel_backend
from pandas import DataFrame from pandas import DataFrame
@ -62,6 +60,7 @@ class Hyperopt:
hyperopt.start() hyperopt.start()
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Dict[str, Any]) -> None:
self.config = config self.config = config
self.backtesting = Backtesting(self.config) self.backtesting = Backtesting(self.config)
@ -75,16 +74,16 @@ class Hyperopt:
'hyperopt_results.pickle') 'hyperopt_results.pickle')
self.tickerdata_pickle = (self.config['user_data_dir'] / 'hyperopt_results' / self.tickerdata_pickle = (self.config['user_data_dir'] / 'hyperopt_results' /
'hyperopt_tickerdata.pkl') 'hyperopt_tickerdata.pkl')
self.effort = config.get('epochs', 0) or 1 self.total_epochs = config['epochs'] if 'epochs' in config else 0
self.total_epochs = 9999 self.effort = config['effort'] if 'effort' in config else -1
self.max_epoch = 9999 self.max_epoch = 0
self.search_space_size = 0 self.search_space_size = 0
self.max_epoch_reached = False self.max_epoch_reached = False
self.min_epochs = INITIAL_POINTS self.min_epochs = INITIAL_POINTS
self.current_best_loss = 100 self.current_best_loss = 100
self.current_best_epoch = 0 self.current_best_epoch = 0
self.epochs_since_last_best = [] self.epochs_since_last_best: List = []
self.avg_best_occurrence = 0 self.avg_best_occurrence = 0
if not self.config.get('hyperopt_continue'): if not self.config.get('hyperopt_continue'):
@ -100,6 +99,10 @@ class Hyperopt:
self.opt: Optimizer self.opt: Optimizer
self.opt = None self.opt = None
self.f_val: List = [] self.f_val: List = []
self.to_ask: deque
self.to_ask = deque()
self.tell: Callable
self.tell = None
# Populate functions here (hasattr is slow so should not be run during "regular" operations) # Populate functions here (hasattr is slow so should not be run during "regular" operations)
if hasattr(self.custom_hyperopt, 'populate_indicators'): if hasattr(self.custom_hyperopt, 'populate_indicators'):
@ -163,6 +166,7 @@ class Hyperopt:
Save hyperopt trials to file Save hyperopt trials to file
""" """
num_trials = len(self.trials) num_trials = len(self.trials)
print()
if num_trials > self.num_trials_saved: if num_trials > self.num_trials_saved:
logger.info(f"Saving {num_trials} {plural(num_trials, 'epoch')}.") logger.info(f"Saving {num_trials} {plural(num_trials, 'epoch')}.")
dump(self.trials, self.trials_file) dump(self.trials, self.trials_file)
@ -276,8 +280,8 @@ class Hyperopt:
""" """
is_best = results['is_best'] is_best = results['is_best']
if self.print_all or is_best: if self.print_all or is_best:
self.print_results_explanation(results, self.total_epochs, self.print_all, self.print_results_explanation(results, self.total_epochs or self.max_epoch,
self.print_colorized) self.print_all, self.print_colorized)
@staticmethod @staticmethod
def print_results_explanation(results, total_epochs, highlight_best: bool, def print_results_explanation(results, total_epochs, highlight_best: bool,
@ -386,10 +390,10 @@ class Hyperopt:
position_stacking=self.position_stacking, position_stacking=self.position_stacking,
) )
return self._get_results_dict(backtesting_results, min_date, max_date, params_dict, return self._get_results_dict(backtesting_results, min_date, max_date, params_dict,
params_details) params_details, raw_params)
def _get_results_dict(self, backtesting_results, min_date, max_date, params_dict, def _get_results_dict(self, backtesting_results, min_date, max_date, params_dict,
params_details): params_details, raw_params):
results_metrics = self._calculate_results_metrics(backtesting_results) results_metrics = self._calculate_results_metrics(backtesting_results)
results_explanation = self._format_results_explanation_string(results_metrics) results_explanation = self._format_results_explanation_string(results_metrics)
@ -413,6 +417,7 @@ class Hyperopt:
'results_metrics': results_metrics, 'results_metrics': results_metrics,
'results_explanation': results_explanation, 'results_explanation': results_explanation,
'total_profit': total_profit, 'total_profit': total_profit,
'asked': raw_params,
} }
def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict:
@ -448,38 +453,51 @@ class Hyperopt:
random_state=self.random_state, random_state=self.random_state,
) )
def run_optimizer_parallel(self, parallel, tries: int, first_try: int) -> List: def run_optimizer_parallel(self, parallel: Parallel, tries: int, first_try: int,
jobs: int) -> List:
result = parallel( result = parallel(
delayed(wrap_non_picklable_objects(self.parallel_objective))(asked, i) delayed(wrap_non_picklable_objects(self.parallel_objective))(asked, i)
for asked, i in zip(self.opt_generator(), range(first_try, first_try + tries))) for asked, i in zip(self.opt_generator(jobs, tries), range(
first_try, first_try + tries)))
return result return result
def opt_generator(self): def opt_generator(self, jobs: int, tries: int):
while True: while True:
if self.f_val: if self.f_val:
# print("opt.tell(): ", # print("opt.tell(): ", [v['asked'] for v in self.f_val],
# [v['params_dict'] for v in self.f_val], [v['loss'] for v in self.f_val]) # [v['loss'] for v in self.f_val])
functools.partial(self.opt.tell, self.tell = functools.partial(self.opt.tell, [v['asked'] for v in self.f_val],
([v['params_dict'] [v['loss'] for v in self.f_val])
for v in self.f_val], [v['loss'] for v in self.f_val]))
self.f_val = [] self.f_val = []
yield self.opt.ask()
if not self.to_ask:
self.opt.update_next()
self.to_ask.extend(self.opt.ask(n_points=tries))
self.fit = True
yield self.to_ask.popleft()
# yield self.opt.ask()
def parallel_objective(self, asked, n): def parallel_objective(self, asked, n):
self.log_results_immediate(n) self.log_results_immediate(n)
return self.generate_optimizer(asked) return self.generate_optimizer(asked)
def parallel_callback(self, f_val): def parallel_callback(self, f_val):
if self.tell:
self.tell(fit=self.fit)
self.tell = None
self.fit = False
self.f_val.extend(f_val) self.f_val.extend(f_val)
def log_results_immediate(self, n) -> None: def log_results_immediate(self, n) -> None:
print('.', end='') print('.', end='')
sys.stdout.flush() sys.stdout.flush()
def log_results(self, f_val, frame_start, max_epoch) -> None: def log_results(self, f_val, frame_start, total_epochs: int) -> None:
""" """
Log results if it is better than any previous evaluation Log results if it is better than any previous evaluation
""" """
print()
current = frame_start + 1
for i, v in enumerate(f_val): for i, v in enumerate(f_val):
is_best = self.is_best_loss(v, self.current_best_loss) is_best = self.is_best_loss(v, self.current_best_loss)
current = frame_start + i + 1 current = frame_start + i + 1
@ -493,15 +511,10 @@ class Hyperopt:
self.print_results(v) self.print_results(v)
self.trials.append(v) self.trials.append(v)
# Save results after every batch # Save results after every batch
print('\n')
self.save_trials() self.save_trials()
# give up if no best since max epochs # give up if no best since max epochs
if current > self.max_epoch: if current + 1 > (total_epochs or self.max_epoch):
self.max_epoch_reached = True self.max_epoch_reached = True
# testing trapdoor
if os.getenv('FQT_HYPEROPT_TRAP'):
logger.debug('bypassing hyperopt loop')
self.max_epoch = 1
@staticmethod @staticmethod
def load_previous_results(trials_file: Path) -> List: def load_previous_results(trials_file: Path) -> List:
@ -522,7 +535,7 @@ class Hyperopt:
return random_state or random.randint(1, 2**16 - 1) return random_state or random.randint(1, 2**16 - 1)
@staticmethod @staticmethod
def calc_epochs(dimensions: List[Dimension], config_jobs: int, effort: int): def calc_epochs(dimensions: List[Dimension], config_jobs: int, effort: int, total_epochs: int):
""" Compute a reasonable number of initial points and """ Compute a reasonable number of initial points and
a minimum number of epochs to evaluate """ a minimum number of epochs to evaluate """
n_dimensions = len(dimensions) n_dimensions = len(dimensions)
@ -543,16 +556,18 @@ class Hyperopt:
if search_space_size < config_jobs: if search_space_size < config_jobs:
# don't waste if the space is small # don't waste if the space is small
n_initial_points = config_jobs n_initial_points = config_jobs
elif total_epochs > 0:
n_initial_points = total_epochs // 3 if total_epochs > config_jobs * 3 else config_jobs
min_epochs = n_initial_points
else: else:
# extract coefficients from the search space and the jobs count # extract coefficients from the search space and the jobs count
log_sss = int(log(search_space_size, 10)) log_sss = int(log(search_space_size, 10))
log_jobs = int(log(config_jobs, 2)) log_jobs = int(log(config_jobs, 2)) if config_jobs > 4 else 2
log_jobs = 2 if log_jobs < 0 else log_jobs
jobs_ip = log_jobs * log_sss jobs_ip = log_jobs * log_sss
# never waste # never waste
n_initial_points = log_sss if jobs_ip > search_space_size else jobs_ip n_initial_points = log_sss if jobs_ip > search_space_size else jobs_ip
# it shall run for this much, I say # it shall run for this much, I say
min_epochs = max(2 * n_initial_points, 3 * config_jobs) * effort min_epochs = int(max(2 * n_initial_points, 3 * config_jobs) * (1 + effort / 10))
return n_initial_points, min_epochs, search_space_size return n_initial_points, min_epochs, search_space_size
def update_max_epoch(self, val: Dict, current: int): def update_max_epoch(self, val: Dict, current: int):
@ -563,11 +578,12 @@ class Hyperopt:
self.avg_best_occurrence = (sum(self.epochs_since_last_best) // self.avg_best_occurrence = (sum(self.epochs_since_last_best) //
len(self.epochs_since_last_best)) len(self.epochs_since_last_best))
self.current_best_epoch = current self.current_best_epoch = current
self.max_epoch = (self.current_best_epoch + self.avg_best_occurrence + self.max_epoch = int(
self.min_epochs) * self.effort (self.current_best_epoch + self.avg_best_occurrence + self.min_epochs) *
(1 + self.effort / 10))
if self.max_epoch > self.search_space_size: if self.max_epoch > self.search_space_size:
self.max_epoch = self.search_space_size self.max_epoch = self.search_space_size
print('\n') print()
logger.info(f'Max epochs set to: {self.max_epoch}') logger.info(f'Max epochs set to: {self.max_epoch}')
def start(self) -> None: def start(self) -> None:
@ -599,47 +615,53 @@ class Hyperopt:
self.dimensions: List[Dimension] = self.hyperopt_space() self.dimensions: List[Dimension] = self.hyperopt_space()
self.n_initial_points, self.min_epochs, self.search_space_size = self.calc_epochs( self.n_initial_points, self.min_epochs, self.search_space_size = self.calc_epochs(
self.dimensions, config_jobs, self.effort) self.dimensions, config_jobs, self.effort, self.total_epochs)
logger.info(f"Min epochs set to: {self.min_epochs}") logger.info(f"Min epochs set to: {self.min_epochs}")
self.max_epoch = self.min_epochs if self.total_epochs < 1:
self.avg_best_occurrence = self.max_epoch self.max_epoch = int(self.min_epochs + len(self.trials))
else:
self.max_epoch = self.n_initial_points
self.avg_best_occurrence = self.min_epochs
logger.info(f'Initial points: {self.n_initial_points}') logger.info(f'Initial points: {self.n_initial_points}')
self.opt = self.get_optimizer(self.dimensions, config_jobs, self.n_initial_points) self.opt = self.get_optimizer(self.dimensions, config_jobs, self.n_initial_points)
# last_frame_len = (self.total_epochs - 1) % self.avg_best_occurrence
if self.print_colorized: if self.print_colorized:
colorama_init(autoreset=True) colorama_init(autoreset=True)
try: try:
register_parallel_backend('custom', CustomImmediateResultBackend) register_parallel_backend('custom', CustomImmediateResultBackend)
with parallel_backend('custom'): with parallel_backend('custom'):
with Parallel(n_jobs=config_jobs, verbose=0) as parallel: with Parallel(n_jobs=config_jobs, verbose=0) as parallel:
for frame in range(self.total_epochs): while True:
epochs_so_far = len(self.trials) # update epochs count
# pad the frame length to the number of jobs to avoid desaturation epochs_so_far = len(self.trials)
frame_len = (self.avg_best_occurrence + config_jobs - # pad the frame length to the number of jobs to avoid desaturation
self.avg_best_occurrence % config_jobs) frame_len = (self.avg_best_occurrence + config_jobs -
print( self.avg_best_occurrence % config_jobs)
f"{epochs_so_far+1}-{epochs_so_far+self.avg_best_occurrence}" # don't go over the limit
f"/{self.total_epochs}: ", if epochs_so_far + frame_len > (self.total_epochs or self.max_epoch):
end='') frame_len = (self.total_epochs or self.max_epoch) - epochs_so_far
f_val = self.run_optimizer_parallel(parallel, frame_len, epochs_so_far) print(
self.log_results(f_val, epochs_so_far, self.total_epochs) f"{epochs_so_far+1}-{epochs_so_far+frame_len}"
if self.max_epoch_reached: f"/{self.total_epochs}: ",
logger.info("Max epoch reached, terminating.") end='')
break f_val = self.run_optimizer_parallel(parallel, frame_len, epochs_so_far,
config_jobs)
self.log_results(f_val, epochs_so_far, self.total_epochs or self.max_epoch)
if self.max_epoch_reached:
logger.info("Max epoch reached, terminating.")
break
except KeyboardInterrupt: except KeyboardInterrupt:
print("User interrupted..") print("User interrupted..")
self.save_trials(final=True) self.save_trials(final=True)
if self.trials: if self.trials:
sorted_trials = sorted(self.trials, key=itemgetter('loss')) sorted_trials = sorted(self.trials, key=itemgetter('loss'))
results = sorted_trials[0] results = sorted_trials[0]
self.print_epoch_details(results, self.total_epochs, self.print_json) self.print_epoch_details(results, self.max_epoch, self.print_json)
else: else:
# This is printed when Ctrl+C is pressed quickly, before first epochs have # This is printed when Ctrl+C is pressed quickly, before first epochs have
# a chance to be evaluated. # a chance to be evaluated.

View File

@ -1,6 +1,7 @@
from joblib._parallel_backends import LokyBackend from joblib._parallel_backends import LokyBackend
from typing import Any
hyperopt = None hyperopt: Any = None
class MultiCallback: class MultiCallback: