From cd2336887cc74b6a518d84f32d7aa6f1691b4824 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Sep 2019 17:14:04 +0200 Subject: [PATCH 001/157] Add first version with shared parent parsers --- freqtrade/configuration/arguments.py | 65 ++++++++++++++++------------ 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 6e2ecea2e..124bf3b75 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -12,7 +12,7 @@ ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_dat ARGS_STRATEGY = ["strategy", "strategy_path"] -ARGS_MAIN = ARGS_COMMON + ARGS_STRATEGY + ["db_url", "sd_notify"] +ARGS_MAIN = ARGS_STRATEGY + ["db_url", "sd_notify"] ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", "max_open_trades", "stake_amount"] @@ -51,11 +51,6 @@ class Arguments: def __init__(self, args: Optional[List[str]]) -> None: self.args = args self._parsed_arg: Optional[argparse.Namespace] = None - self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') - - def _load_args(self) -> None: - self._build_args(optionlist=ARGS_MAIN) - self._build_subcommands() def get_parsed_arg(self) -> Dict[str, Any]: """ @@ -63,7 +58,7 @@ class Arguments: :return: List[str] List of arguments """ if self._parsed_arg is None: - self._load_args() + self._build_subcommands() self._parsed_arg = self._parse_args() return vars(self._parsed_arg) @@ -86,7 +81,6 @@ class Arguments: return parsed_arg def _build_args(self, optionlist, parser=None): - parser = parser or self.parser for val in optionlist: opt = AVAILABLE_CLI_OPTIONS[val] @@ -97,61 +91,76 @@ class Arguments: Builds and attaches all subcommands. :return: None """ + # Build shared arguments (as group Common Options) + _common_parser = argparse.ArgumentParser(add_help=False) + group = _common_parser.add_argument_group("Common Options") + self._build_args(optionlist=ARGS_COMMON, parser=group) + + # Build main command + self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot', + parents=[_common_parser]) + self._build_args(optionlist=ARGS_MAIN, parser=self.parser) + from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge from freqtrade.utils import start_create_userdir, start_download_data, start_list_exchanges subparsers = self.parser.add_subparsers(dest='subparser') # Add backtesting subcommand - backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.') + backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.', + parents=[_common_parser]) backtesting_cmd.set_defaults(func=start_backtesting) self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) # Add edge subcommand - edge_cmd = subparsers.add_parser('edge', help='Edge module.') + edge_cmd = subparsers.add_parser('edge', help='Edge module.', parents=[_common_parser]) edge_cmd.set_defaults(func=start_edge) self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd) # Add hyperopt subcommand - hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.') + hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.', + parents=[_common_parser], + ) hyperopt_cmd.set_defaults(func=start_hyperopt) self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd) # add create-userdir subcommand create_userdir_cmd = subparsers.add_parser('create-userdir', - help="Create user-data directory.") + help="Create user-data directory.", + + ) create_userdir_cmd.set_defaults(func=start_create_userdir) self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd) # Add list-exchanges subcommand - list_exchanges_cmd = subparsers.add_parser( - 'list-exchanges', - help='Print available exchanges.' - ) + list_exchanges_cmd = subparsers.add_parser('list-exchanges', + help='Print available exchanges.', + parents=[_common_parser], + ) list_exchanges_cmd.set_defaults(func=start_list_exchanges) self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd) # Add download-data subcommand - download_data_cmd = subparsers.add_parser( - 'download-data', - help='Download backtesting data.' - ) + download_data_cmd = subparsers.add_parser('download-data', + help='Download backtesting data.', + parents=[_common_parser], + ) download_data_cmd.set_defaults(func=start_download_data) self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd) # Add Plotting subcommand from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit - plot_dataframe_cmd = subparsers.add_parser( - 'plot-dataframe', - help='Plot candles with indicators.' - ) + plot_dataframe_cmd = subparsers.add_parser('plot-dataframe', + help='Plot candles with indicators.', + parents=[_common_parser], + ) plot_dataframe_cmd.set_defaults(func=start_plot_dataframe) self._build_args(optionlist=ARGS_PLOT_DATAFRAME, parser=plot_dataframe_cmd) # Plot profit - plot_profit_cmd = subparsers.add_parser( - 'plot-profit', - help='Generate plot showing profits.' - ) + plot_profit_cmd = subparsers.add_parser('plot-profit', + help='Generate plot showing profits.', + parents=[_common_parser], + ) plot_profit_cmd.set_defaults(func=start_plot_profit) self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd) From 2a535b72ff6e79b9bc5c218ce97d4b7ec40aede1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 11:15:51 +0200 Subject: [PATCH 002/157] Parser should not have default --- freqtrade/configuration/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 124bf3b75..d26d64c40 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -80,7 +80,7 @@ class Arguments: return parsed_arg - def _build_args(self, optionlist, parser=None): + def _build_args(self, optionlist, parser): for val in optionlist: opt = AVAILABLE_CLI_OPTIONS[val] From cb37f43277bb4d3f81d867888ba7bd63550c2df5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 11:16:14 +0200 Subject: [PATCH 003/157] Add trade subparser (and make subparser a requirement) --- freqtrade/configuration/arguments.py | 14 ++++++++++---- freqtrade/utils.py | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index d26d64c40..ff28a6406 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -99,12 +99,19 @@ class Arguments: # Build main command self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot', parents=[_common_parser]) - self._build_args(optionlist=ARGS_MAIN, parser=self.parser) from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge - from freqtrade.utils import start_create_userdir, start_download_data, start_list_exchanges + from freqtrade.utils import (start_create_userdir, start_download_data, + start_list_exchanges, start_trading) + from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit - subparsers = self.parser.add_subparsers(dest='subparser') + subparsers = self.parser.add_subparsers(dest='subparser', required=True) + + # Add trade subcommand + trade_cmd = subparsers.add_parser('trade', help='Trade module.', + parents=[_common_parser]) + trade_cmd.set_defaults(func=start_trading) + self._build_args(optionlist=ARGS_MAIN, parser=trade_cmd) # Add backtesting subcommand backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.', @@ -149,7 +156,6 @@ class Arguments: self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd) # Add Plotting subcommand - from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit plot_dataframe_cmd = subparsers.add_parser('plot-dataframe', help='Plot candles with indicators.', parents=[_common_parser], diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 6ce5e888c..8e57606da 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -33,6 +33,17 @@ def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str return config +def start_trading(args: Dict[str, Any]) -> int: + """ + Main entry point for trading mode + """ + from freqtrade.worker import Worker + # Load and run worker + worker = Worker(args) + worker.run() + return 0 + + def start_list_exchanges(args: Dict[str, Any]) -> None: """ Print available exchanges @@ -47,7 +58,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: f"{', '.join(available_exchanges())}") -def start_create_userdir(args: Dict[str, Any]) -> None: +def start_create_userdir(args: Dict[str, Any]) -> int: """ Create "user_data" directory to contain user data strategies, hyperopts, ...) :param args: Cli args from Arguments() @@ -57,7 +68,7 @@ def start_create_userdir(args: Dict[str, Any]) -> None: create_userdata_dir(args["user_data_dir"], create_dir=True) else: logger.warning("`create-userdir` requires --userdir to be set.") - sys.exit(1) + return 1 def start_download_data(args: Dict[str, Any]) -> None: From 8664e7f7d35d85dae15862d6d42bf292446bd198 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 11:19:08 +0200 Subject: [PATCH 004/157] Have main.py support only subcommand mode --- freqtrade/main.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 4d6f0dce7..543aab169 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -15,7 +15,6 @@ from typing import Any, List from freqtrade import OperationalException from freqtrade.configuration import Arguments -from freqtrade.worker import Worker logger = logging.getLogger('freqtrade') @@ -33,16 +32,9 @@ def main(sysargv: List[str] = None) -> None: arguments = Arguments(sysargv) args = arguments.get_parsed_arg() - # A subcommand has been issued. - # Means if Backtesting or Hyperopt have been called we exit the bot + # Call subcommand. if 'func' in args: - args['func'](args) - # TODO: fetch return_code as returned by the command function here - return_code = 0 - else: - # Load and run worker - worker = Worker(args) - worker.run() + return_code = args['func'](args) except SystemExit as e: return_code = e From 0f2e277f80b68a82272e62348f59a17418894c9c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 11:19:42 +0200 Subject: [PATCH 005/157] Rename subparser variable to command --- freqtrade/configuration/arguments.py | 2 +- tests/test_arguments.py | 4 ++-- tests/test_main.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index ff28a6406..8f6924032 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -105,7 +105,7 @@ class Arguments: start_list_exchanges, start_trading) from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit - subparsers = self.parser.add_subparsers(dest='subparser', required=True) + subparsers = self.parser.add_subparsers(dest='command', required=True) # Add trade subcommand trade_cmd = subparsers.add_parser('trade', help='Trade module.', diff --git a/tests/test_arguments.py b/tests/test_arguments.py index cf0104c01..1b04d9419 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -106,7 +106,7 @@ def test_parse_args_backtesting_custom() -> None: call_args = Arguments(args).get_parsed_arg() assert call_args["config"] == ['test_conf.json'] assert call_args["verbosity"] == 0 - assert call_args["subparser"] == 'backtesting' + assert call_args["command"] == 'backtesting' assert call_args["func"] is not None assert call_args["ticker_interval"] == '1m' assert type(call_args["strategy_list"]) is list @@ -124,7 +124,7 @@ def test_parse_args_hyperopt_custom() -> None: assert call_args["config"] == ['test_conf.json'] assert call_args["epochs"] == 20 assert call_args["verbosity"] == 0 - assert call_args["subparser"] == 'hyperopt' + assert call_args["command"] == 'hyperopt' assert call_args["spaces"] == ['buy'] assert call_args["func"] is not None assert callable(call_args["func"]) diff --git a/tests/test_main.py b/tests/test_main.py index d73edc0da..10d7d3216 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -29,7 +29,7 @@ def test_parse_args_backtesting(mocker) -> None: call_args = backtesting_mock.call_args[0][0] assert call_args["config"] == ['config.json'] assert call_args["verbosity"] == 0 - assert call_args["subparser"] == 'backtesting' + assert call_args["command"] == 'backtesting' assert call_args["func"] is not None assert callable(call_args["func"]) assert call_args["ticker_interval"] is None @@ -45,7 +45,7 @@ def test_main_start_hyperopt(mocker) -> None: call_args = hyperopt_mock.call_args[0][0] assert call_args["config"] == ['config.json'] assert call_args["verbosity"] == 0 - assert call_args["subparser"] == 'hyperopt' + assert call_args["command"] == 'hyperopt' assert call_args["func"] is not None assert callable(call_args["func"]) From 03add90c9494e0a848b73b31e73ecaa05763cfcd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 13:18:52 +0200 Subject: [PATCH 006/157] Adjust some tests to new call-method --- tests/optimize/test_backtesting.py | 12 ++++++------ tests/test_main.py | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index fa40809d8..1a52f851e 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -169,9 +169,9 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> patched_configuration_load_config_file(mocker, default_conf) args = [ + 'backtesting', '--config', 'config.json', '--strategy', 'DefaultStrategy', - 'backtesting' ] config = setup_configuration(get_args(args), RunMode.BACKTEST) @@ -202,10 +202,10 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> ) args = [ + 'backtesting', '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', - 'backtesting', '--ticker-interval', '1m', '--enable-position-stacking', '--disable-max-market-positions', @@ -250,9 +250,9 @@ def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog patched_configuration_load_config_file(mocker, default_conf) args = [ + 'backtesting', '--config', 'config.json', '--strategy', 'DefaultStrategy', - 'backtesting' ] with pytest.raises(DependencyException, match=r'.*stake amount.*'): @@ -267,9 +267,9 @@ def test_start(mocker, fee, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) args = [ + 'backtesting', '--config', 'config.json', '--strategy', 'DefaultStrategy', - 'backtesting' ] args = get_args(args) start_backtesting(args) @@ -812,10 +812,10 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir): patched_configuration_load_config_file(mocker, default_conf) args = [ + 'backtesting', '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', str(testdatadir), - 'backtesting', '--ticker-interval', '1m', '--timerange', '-100', '--enable-position-stacking', @@ -859,9 +859,9 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): patched_configuration_load_config_file(mocker, default_conf) args = [ + 'backtesting', '--config', 'config.json', '--datadir', str(testdatadir), - 'backtesting', '--ticker-interval', '1m', '--timerange', '-100', '--enable-position-stacking', diff --git a/tests/test_main.py b/tests/test_main.py index 10d7d3216..c5e1c8901 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -58,7 +58,7 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - args = ['-c', 'config.json.example'] + args = ['trade', '-c', 'config.json.example'] # Test Main + the KeyboardInterrupt exception with pytest.raises(SystemExit): @@ -75,7 +75,7 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - args = ['-c', 'config.json.example'] + args = ['trade', '-c', 'config.json.example'] # Test Main + the KeyboardInterrupt exception with pytest.raises(SystemExit): @@ -95,7 +95,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - args = ['-c', 'config.json.example'] + args = ['trade', '-c', 'config.json.example'] # Test Main + the KeyboardInterrupt exception with pytest.raises(SystemExit): @@ -114,15 +114,15 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None: OperationalException("Oh snap!")]) mocker.patch('freqtrade.worker.Worker._worker', worker_mock) patched_configuration_load_config_file(mocker, default_conf) - reconfigure_mock = mocker.patch('freqtrade.main.Worker._reconfigure', MagicMock()) + reconfigure_mock = mocker.patch('freqtrade.worker.Worker._reconfigure', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - args = Arguments(['-c', 'config.json.example']).get_parsed_arg() + args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg() worker = Worker(args=args, config=default_conf) with pytest.raises(SystemExit): - main(['-c', 'config.json.example']) + main(['trade', '-c', 'config.json.example']) assert log_has('Using config: config.json.example ...', caplog) assert worker_mock.call_count == 4 @@ -141,7 +141,7 @@ def test_reconfigure(mocker, default_conf) -> None: mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) - args = Arguments(['-c', 'config.json.example']).get_parsed_arg() + args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg() worker = Worker(args=args, config=default_conf) freqtrade = worker.freqtrade From 1b25b5f590e3d50678b853003c9ca06e77ef2a82 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 13:19:05 +0200 Subject: [PATCH 007/157] Remove duplicate short-form `-s` --- freqtrade/configuration/cli_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index cb07dbdba..24230f1b3 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -171,7 +171,7 @@ AVAILABLE_CLI_OPTIONS = { default=constants.HYPEROPT_EPOCH, ), "spaces": Arg( - '-s', '--spaces', + '--spaces', help='Specify which parameters to hyperopt. Space-separated list. ' 'Default: `%(default)s`.', choices=['all', 'buy', 'sell', 'roi', 'stoploss'], From d62a4d3566eee173e923f51d76cf1d2e5c0e914b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 13:19:21 +0200 Subject: [PATCH 008/157] Fix some minor problems --- freqtrade/configuration/arguments.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 8f6924032..d0db162b9 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -14,8 +14,8 @@ ARGS_STRATEGY = ["strategy", "strategy_path"] ARGS_MAIN = ARGS_STRATEGY + ["db_url", "sd_notify"] -ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", - "max_open_trades", "stake_amount"] +ARGS_COMMON_OPTIMIZE = ARGS_STRATEGY + ["ticker_interval", "timerange", + "max_open_trades", "stake_amount"] ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "strategy_list", "export", "exportfilename"] @@ -75,7 +75,7 @@ class Arguments: # (see https://bugs.python.org/issue16399) # Allow no-config for certain commands (like downloading / plotting) if (parsed_arg.config is None and ((Path.cwd() / constants.DEFAULT_CONFIG).is_file() or - not ('subparser' in parsed_arg and parsed_arg.subparser in NO_CONF_REQURIED))): + not ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED))): parsed_arg.config = [constants.DEFAULT_CONFIG] return parsed_arg @@ -120,7 +120,8 @@ class Arguments: self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) # Add edge subcommand - edge_cmd = subparsers.add_parser('edge', help='Edge module.', parents=[_common_parser]) + edge_cmd = subparsers.add_parser('edge', help='Edge module.', + parents=[_common_parser]) edge_cmd.set_defaults(func=start_edge) self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd) From db3b974479976273ba54ce919ed07521d3c1311c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 13:19:40 +0200 Subject: [PATCH 009/157] Fix calling sequence --- tests/optimize/test_edge_cli.py | 6 +++--- tests/test_configuration.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 97103da55..1d2faead0 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -15,9 +15,9 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> patched_configuration_load_config_file(mocker, default_conf) args = [ + 'edge', '--config', 'config.json', '--strategy', 'DefaultStrategy', - 'edge' ] config = setup_configuration(get_args(args), RunMode.EDGE) @@ -45,10 +45,10 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N ) args = [ + 'edge', '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', - 'edge', '--ticker-interval', '1m', '--timerange', ':100', '--stoplosses=-0.01,-0.10,-0.001' @@ -79,9 +79,9 @@ def test_start(mocker, fee, edge_conf, caplog) -> None: patched_configuration_load_config_file(mocker, edge_conf) args = [ + 'edge', '--config', 'config.json', '--strategy', 'DefaultStrategy', - 'edge' ] args = get_args(args) start_edge(args) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 330b82d39..522b1c1ee 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -806,8 +806,8 @@ def test_pairlist_resolving(): def test_pairlist_resolving_with_config(mocker, default_conf): patched_configuration_load_config_file(mocker, default_conf) arglist = [ - '--config', 'config.json', 'download-data', + '--config', 'config.json', ] args = Arguments(arglist).get_parsed_arg() @@ -820,8 +820,8 @@ def test_pairlist_resolving_with_config(mocker, default_conf): # Override pairs arglist = [ - '--config', 'config.json', 'download-data', + '--config', 'config.json', '--pairs', 'ETH/BTC', 'XRP/BTC', ] @@ -842,8 +842,8 @@ def test_pairlist_resolving_with_config_pl(mocker, default_conf): mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock())) arglist = [ - '--config', 'config.json', 'download-data', + '--config', 'config.json', '--pairs-file', 'pairs.json', ] @@ -864,8 +864,8 @@ def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf): mocker.patch.object(Path, "exists", MagicMock(return_value=False)) arglist = [ - '--config', 'config.json', 'download-data', + '--config', 'config.json', '--pairs-file', 'pairs.json', ] From e8106f379222e9353bdb93dcc1504cc8d5f2b809 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 13:38:26 +0200 Subject: [PATCH 010/157] Fix most tests to have trade as default argument --- tests/test_arguments.py | 22 +++++++++++----------- tests/test_configuration.py | 30 ++++++++++++++++++------------ tests/test_plotting.py | 4 ++-- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 1b04d9419..53602574e 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -25,27 +25,27 @@ def test_parse_args_defaults() -> None: def test_parse_args_config() -> None: - args = Arguments(['-c', '/dev/null']).get_parsed_arg() + args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg() assert args["config"] == ['/dev/null'] - args = Arguments(['--config', '/dev/null']).get_parsed_arg() + args = Arguments(['trade', '--config', '/dev/null']).get_parsed_arg() assert args["config"] == ['/dev/null'] - args = Arguments(['--config', '/dev/null', + args = Arguments(['trade', '--config', '/dev/null', '--config', '/dev/zero'],).get_parsed_arg() assert args["config"] == ['/dev/null', '/dev/zero'] def test_parse_args_db_url() -> None: - args = Arguments(['--db-url', 'sqlite:///test.sqlite']).get_parsed_arg() + args = Arguments(['trade', '--db-url', 'sqlite:///test.sqlite']).get_parsed_arg() assert args["db_url"] == 'sqlite:///test.sqlite' def test_parse_args_verbose() -> None: - args = Arguments(['-v']).get_parsed_arg() + args = Arguments(['trade', '-v']).get_parsed_arg() assert args["verbosity"] == 1 - args = Arguments(['--verbose']).get_parsed_arg() + args = Arguments(['trade', '--verbose']).get_parsed_arg() assert args["verbosity"] == 1 @@ -67,7 +67,7 @@ def test_parse_args_invalid() -> None: def test_parse_args_strategy() -> None: - args = Arguments(['--strategy', 'SomeStrategy']).get_parsed_arg() + args = Arguments(['trade', '--strategy', 'SomeStrategy']).get_parsed_arg() assert args["strategy"] == 'SomeStrategy' @@ -77,7 +77,7 @@ def test_parse_args_strategy_invalid() -> None: def test_parse_args_strategy_path() -> None: - args = Arguments(['--strategy-path', '/some/path']).get_parsed_arg() + args = Arguments(['trade', '--strategy-path', '/some/path']).get_parsed_arg() assert args["strategy_path"] == '/some/path' @@ -96,8 +96,8 @@ def test_parse_args_backtesting_invalid() -> None: def test_parse_args_backtesting_custom() -> None: args = [ - '-c', 'test_conf.json', 'backtesting', + '-c', 'test_conf.json', '--ticker-interval', '1m', '--strategy-list', 'DefaultStrategy', @@ -115,8 +115,8 @@ def test_parse_args_backtesting_custom() -> None: def test_parse_args_hyperopt_custom() -> None: args = [ - '-c', 'test_conf.json', 'hyperopt', + '-c', 'test_conf.json', '--epochs', '20', '--spaces', 'buy' ] @@ -132,8 +132,8 @@ def test_parse_args_hyperopt_custom() -> None: def test_download_data_options() -> None: args = [ - '--datadir', 'datadir/directory', 'download-data', + '--datadir', 'datadir/directory', '--pairs-file', 'file_with_pairs', '--days', '30', '--exchange', 'binance' diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 522b1c1ee..3c3ad3026 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -65,7 +65,7 @@ def test_load_config_file(default_conf, mocker, caplog) -> None: def test__args_to_config(caplog): - arg_list = ['--strategy-path', 'TestTest'] + arg_list = ['trade', '--strategy-path', 'TestTest'] args = Arguments(arg_list).get_parsed_arg() configuration = Configuration(args) config = {} @@ -93,7 +93,7 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: default_conf['max_open_trades'] = 0 patched_configuration_load_config_file(mocker, default_conf) - args = Arguments([]).get_parsed_arg() + args = Arguments(['trade']).get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() @@ -118,7 +118,7 @@ def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None: configsmock ) - arg_list = ['-c', 'test_conf.json', '--config', 'test2_conf.json', ] + arg_list = ['trade', '-c', 'test_conf.json', '--config', 'test2_conf.json', ] args = Arguments(arg_list).get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() @@ -184,7 +184,7 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> default_conf['max_open_trades'] = -1 patched_configuration_load_config_file(mocker, default_conf) - args = Arguments([]).get_parsed_arg() + args = Arguments(['trade']).get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() @@ -208,7 +208,7 @@ def test_load_config_file_exception(mocker) -> None: def test_load_config(default_conf, mocker) -> None: patched_configuration_load_config_file(mocker, default_conf) - args = Arguments([]).get_parsed_arg() + args = Arguments(['trade']).get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() @@ -221,6 +221,7 @@ def test_load_config_with_params(default_conf, mocker) -> None: patched_configuration_load_config_file(mocker, default_conf) arglist = [ + 'trade', '--strategy', 'TestStrategy', '--strategy-path', '/some/path', '--db-url', 'sqlite:///someurl', @@ -240,6 +241,7 @@ def test_load_config_with_params(default_conf, mocker) -> None: patched_configuration_load_config_file(mocker, conf) arglist = [ + 'trade', '--strategy', 'TestStrategy', '--strategy-path', '/some/path' ] @@ -256,6 +258,7 @@ def test_load_config_with_params(default_conf, mocker) -> None: patched_configuration_load_config_file(mocker, conf) arglist = [ + 'trade', '--strategy', 'TestStrategy', '--strategy-path', '/some/path' ] @@ -272,6 +275,7 @@ def test_load_config_with_params(default_conf, mocker) -> None: patched_configuration_load_config_file(mocker, conf) arglist = [ + 'trade', '--strategy', 'TestStrategy', '--strategy-path', '/some/path' ] @@ -290,6 +294,7 @@ def test_load_config_with_params(default_conf, mocker) -> None: patched_configuration_load_config_file(mocker, conf) arglist = [ + 'trade', '--strategy', 'TestStrategy', '--strategy-path', '/some/path' ] @@ -307,7 +312,7 @@ def test_load_custom_strategy(default_conf, mocker) -> None: }) patched_configuration_load_config_file(mocker, default_conf) - args = Arguments([]).get_parsed_arg() + args = Arguments(['trade']).get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() @@ -319,6 +324,7 @@ def test_show_info(default_conf, mocker, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) arglist = [ + 'trade', '--strategy', 'TestStrategy', '--db-url', 'sqlite:///tmp/testdb', ] @@ -335,9 +341,9 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> patched_configuration_load_config_file(mocker, default_conf) arglist = [ + 'backtesting', '--config', 'config.json', '--strategy', 'DefaultStrategy', - 'backtesting' ] args = Arguments(arglist).get_parsed_arg() @@ -373,11 +379,11 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non lambda x, *args, **kwargs: Path(x) ) arglist = [ + 'backtesting', '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', '--userdir', "/tmp/freqtrade", - 'backtesting', '--ticker-interval', '1m', '--enable-position-stacking', '--disable-max-market-positions', @@ -424,8 +430,8 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non patched_configuration_load_config_file(mocker, default_conf) arglist = [ - '--config', 'config.json', 'backtesting', + '--config', 'config.json', '--ticker-interval', '1m', '--export', '/bar/foo', '--strategy-list', @@ -552,7 +558,7 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: # Prevent setting loggers mocker.patch('freqtrade.loggers._set_loggers', MagicMock) - arglist = ['-vvv'] + arglist = ['trade', '-vvv'] args = Arguments(arglist).get_parsed_arg() configuration = Configuration(args) @@ -604,7 +610,7 @@ def test_set_logfile(default_conf, mocker): patched_configuration_load_config_file(mocker, default_conf) arglist = [ - '--logfile', 'test_file.log', + 'trade', '--logfile', 'test_file.log', ] args = Arguments(arglist).get_parsed_arg() configuration = Configuration(args) @@ -620,7 +626,7 @@ def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None: default_conf['forcebuy_enable'] = True patched_configuration_load_config_file(mocker, default_conf) - args = Arguments([]).get_parsed_arg() + args = Arguments(['trade']).get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 9028ab961..cf5a22973 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -281,8 +281,8 @@ def test_generate_profit_graph(testdatadir): def test_start_plot_dataframe(mocker): aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock()) args = [ - "--config", "config.json.example", "plot-dataframe", + "--config", "config.json.example", "--pairs", "ETH/BTC" ] start_plot_dataframe(get_args(args)) @@ -323,8 +323,8 @@ def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir): def test_start_plot_profit(mocker): aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock()) args = [ - "--config", "config.json.example", "plot-profit", + "--config", "config.json.example", "--pairs", "ETH/BTC" ] start_plot_profit(get_args(args)) From ad2fa61765f80e79d8014255887c79343e642117 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 13:40:28 +0200 Subject: [PATCH 011/157] Fix utils test --- freqtrade/utils.py | 2 +- tests/test_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 8e57606da..de167671f 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -68,7 +68,7 @@ def start_create_userdir(args: Dict[str, Any]) -> int: create_userdata_dir(args["user_data_dir"], create_dir=True) else: logger.warning("`create-userdir` requires --userdir to be set.") - return 1 + sys.exit(1) def start_download_data(args: Dict[str, Any]) -> None: diff --git a/tests/test_utils.py b/tests/test_utils.py index c99044610..7922bb624 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -13,7 +13,7 @@ from tests.conftest import get_args, log_has, patch_exchange def test_setup_utils_configuration(): args = [ - '--config', 'config.json.example', + 'list-exchanges', '--config', 'config.json.example', ] config = setup_utils_configuration(get_args(args), RunMode.OTHER) From 0aa73d5b35b09e088e1ab06dfe148702513de964 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 14 Sep 2019 13:47:33 +0200 Subject: [PATCH 012/157] Add test for failing case --- tests/test_arguments.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 53602574e..8ea55dd6a 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -1,5 +1,6 @@ # pragma pylint: disable=missing-docstring, C0103 import argparse +import re import pytest @@ -8,8 +9,16 @@ from freqtrade.configuration.cli_options import check_int_positive # Parse common command-line-arguments. Used for all tools -def test_parse_args_none() -> None: +def test_parse_args_error(capsys) -> None: arguments = Arguments([]) + with pytest.raises(SystemExit): + arguments.get_parsed_arg() + captured = capsys.readouterr() + assert re.search(r".*the following arguments are required.*", captured.err) + + +def test_parse_args_none() -> None: + arguments = Arguments(['trade']) assert isinstance(arguments, Arguments) x = arguments.get_parsed_arg() assert isinstance(x, dict) @@ -17,7 +26,7 @@ def test_parse_args_none() -> None: def test_parse_args_defaults() -> None: - args = Arguments([]).get_parsed_arg() + args = Arguments(['trade']).get_parsed_arg() assert args["config"] == ['config.json'] assert args["strategy_path"] is None assert args["datadir"] is None From 9ef874e979c064d3423f6dd1b75dc971dd96be34 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Sep 2019 06:35:37 +0200 Subject: [PATCH 013/157] Add Custom message during transition period --- freqtrade/configuration/arguments.py | 14 +++++++++----- freqtrade/main.py | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index d0db162b9..4fa493eea 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -74,8 +74,9 @@ class Arguments: # Workaround issue in argparse with action='append' and default value # (see https://bugs.python.org/issue16399) # Allow no-config for certain commands (like downloading / plotting) - if (parsed_arg.config is None and ((Path.cwd() / constants.DEFAULT_CONFIG).is_file() or - not ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED))): + if ('config' in parsed_arg and parsed_arg.config is None and + ((Path.cwd() / constants.DEFAULT_CONFIG).is_file() or + not ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED))): parsed_arg.config = [constants.DEFAULT_CONFIG] return parsed_arg @@ -97,15 +98,18 @@ class Arguments: self._build_args(optionlist=ARGS_COMMON, parser=group) # Build main command - self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot', - parents=[_common_parser]) + self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge from freqtrade.utils import (start_create_userdir, start_download_data, start_list_exchanges, start_trading) from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit - subparsers = self.parser.add_subparsers(dest='command', required=True) + subparsers = self.parser.add_subparsers(dest='command', + # Use custom message when no subhandler is added + # shown from `main.py` + # required=True + ) # Add trade subcommand trade_cmd = subparsers.add_parser('trade', help='Trade module.', diff --git a/freqtrade/main.py b/freqtrade/main.py index 543aab169..d984ff487 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -35,6 +35,13 @@ def main(sysargv: List[str] = None) -> None: # Call subcommand. if 'func' in args: return_code = args['func'](args) + else: + # No subcommand was issued. + raise OperationalException( + "Usage of freqtrade requires a subcommand.\n" + "To use the previous behaviour, run freqtrade with `freqtrade trade [...]`.\n" + "To see a full list of options, please use `freqtrade --help`" + ) except SystemExit as e: return_code = e From 09f18d07b06ac4e7d08a902ea2e84e9dc91f31fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Sep 2019 06:44:07 +0200 Subject: [PATCH 014/157] Adjust some hyperopt tests --- tests/optimize/test_hyperopt.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index c9a112422..8a8e31df3 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -68,8 +68,8 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca patched_configuration_load_config_file(mocker, default_conf) args = [ + 'hyperopt', '--config', 'config.json', - 'hyperopt' ] config = setup_configuration(get_args(args), RunMode.HYPEROPT) @@ -99,9 +99,9 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo ) args = [ + 'hyperopt', '--config', 'config.json', '--datadir', '/foo/bar', - 'hyperopt', '--ticker-interval', '1m', '--timerange', ':100', '--enable-position-stacking', @@ -215,8 +215,8 @@ def test_start(mocker, default_conf, caplog) -> None: patch_exchange(mocker) args = [ - '--config', 'config.json', 'hyperopt', + '--config', 'config.json', '--epochs', '5' ] args = get_args(args) @@ -237,8 +237,8 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: patch_exchange(mocker) args = [ - '--config', 'config.json', 'hyperopt', + '--config', 'config.json', '--epochs', '5' ] args = get_args(args) @@ -254,8 +254,8 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: patch_exchange(mocker) args = [ - '--config', 'config.json', 'hyperopt', + '--config', 'config.json', '--epochs', '5' ] args = get_args(args) From 67b82638dbda9d879987f2012c2d1847c7364c61 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Sep 2019 06:44:20 +0200 Subject: [PATCH 015/157] Move test without command to test_main --- tests/test_arguments.py | 11 ++--------- tests/test_main.py | 8 +++++++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 8ea55dd6a..8cd24fe55 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -4,19 +4,12 @@ import re import pytest +from freqtrade import OperationalException from freqtrade.configuration import Arguments from freqtrade.configuration.cli_options import check_int_positive # Parse common command-line-arguments. Used for all tools -def test_parse_args_error(capsys) -> None: - arguments = Arguments([]) - with pytest.raises(SystemExit): - arguments.get_parsed_arg() - captured = capsys.readouterr() - assert re.search(r".*the following arguments are required.*", captured.err) - - def test_parse_args_none() -> None: arguments = Arguments(['trade']) assert isinstance(arguments, Arguments) @@ -157,8 +150,8 @@ def test_download_data_options() -> None: def test_plot_dataframe_options() -> None: args = [ - '-c', 'config.json.example', 'plot-dataframe', + '-c', 'config.json.example', '--indicators1', 'sma10', 'sma100', '--indicators2', 'macd', 'fastd', 'fastk', '--plot-limit', '30', diff --git a/tests/test_main.py b/tests/test_main.py index c5e1c8901..dac960886 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -11,10 +11,16 @@ from freqtrade.freqtradebot import FreqtradeBot from freqtrade.main import main from freqtrade.state import State from freqtrade.worker import Worker -from tests.conftest import (log_has, patch_exchange, +from tests.conftest import (log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) +def test_parse_args_None(caplog) -> None: + with pytest.raises(SystemExit): + main([]) + assert log_has_re(r"Usage of freqtrade requires a subcommand\.", caplog) + + def test_parse_args_backtesting(mocker) -> None: """ Test that main() can start backtesting and also ensure we can pass some specific arguments From 014881e5504ad7b7ca02aa262e007ed23affc22c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Sep 2019 06:44:39 +0200 Subject: [PATCH 016/157] Allow query version without subcommand --- freqtrade/configuration/arguments.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 4fa493eea..fecf1da07 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -99,6 +99,7 @@ class Arguments: # Build main command self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') + self._build_args(optionlist=['version'], parser=self.parser) from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge from freqtrade.utils import (start_create_userdir, start_download_data, From 0d13e2cb2eabac2011b156d7a6c454c7defde9f7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Sep 2019 11:34:44 +0200 Subject: [PATCH 017/157] Update travis to run new methods --- .travis.yml | 4 ++-- freqtrade/utils.py | 2 +- tests/test_arguments.py | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 405228ab8..81c3de2fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,11 +28,11 @@ jobs: name: pytest - script: - cp config.json.example config.json - - freqtrade --datadir tests/testdata backtesting + - freqtrade backtesting --datadir tests/testdata name: backtest - script: - cp config.json.example config.json - - freqtrade --datadir tests/testdata hyperopt -e 5 + - freqtrade hyperopt --datadir tests/testdata -e 5 name: hyperopt - script: flake8 name: flake8 diff --git a/freqtrade/utils.py b/freqtrade/utils.py index de167671f..6b2b5314b 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -58,7 +58,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: f"{', '.join(available_exchanges())}") -def start_create_userdir(args: Dict[str, Any]) -> int: +def start_create_userdir(args: Dict[str, Any]) -> None: """ Create "user_data" directory to contain user data strategies, hyperopts, ...) :param args: Cli args from Arguments() diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 8cd24fe55..4e01732b3 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -1,10 +1,8 @@ # pragma pylint: disable=missing-docstring, C0103 import argparse -import re import pytest -from freqtrade import OperationalException from freqtrade.configuration import Arguments from freqtrade.configuration.cli_options import check_int_positive From 52523bcd8bd9cd6c5b4a02730bb6d8ca50904793 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2019 16:25:25 +0200 Subject: [PATCH 018/157] Use strategy child parser --- freqtrade/configuration/arguments.py | 29 +++++++++++++++------------- tests/optimize/test_hyperopt.py | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index fecf1da07..d2e5d1fc5 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -12,10 +12,9 @@ ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_dat ARGS_STRATEGY = ["strategy", "strategy_path"] -ARGS_MAIN = ARGS_STRATEGY + ["db_url", "sd_notify"] +ARGS_TRADE = ["db_url", "sd_notify"] -ARGS_COMMON_OPTIMIZE = ARGS_STRATEGY + ["ticker_interval", "timerange", - "max_open_trades", "stake_amount"] +ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", "max_open_trades", "stake_amount"] ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "strategy_list", "export", "exportfilename"] @@ -35,8 +34,9 @@ ARGS_CREATE_USERDIR = ["user_data_dir"] ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "exchange", "timeframes", "erase"] -ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url", - "trade_source", "export", "exportfilename", "timerange", "ticker_interval"] +ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", + "db_url", "trade_source", "export", "exportfilename", + "timerange", "ticker_interval"] ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source", "ticker_interval"] @@ -94,9 +94,13 @@ class Arguments: """ # Build shared arguments (as group Common Options) _common_parser = argparse.ArgumentParser(add_help=False) - group = _common_parser.add_argument_group("Common Options") + group = _common_parser.add_argument_group("Common arguments") self._build_args(optionlist=ARGS_COMMON, parser=group) + _strategy_parser = argparse.ArgumentParser(add_help=False) + strategy_group = _common_parser.add_argument_group("Strategy arguments") + self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group) + # Build main command self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self._build_args(optionlist=['version'], parser=self.parser) @@ -114,25 +118,25 @@ class Arguments: # Add trade subcommand trade_cmd = subparsers.add_parser('trade', help='Trade module.', - parents=[_common_parser]) + parents=[_common_parser, _strategy_parser]) trade_cmd.set_defaults(func=start_trading) - self._build_args(optionlist=ARGS_MAIN, parser=trade_cmd) + self._build_args(optionlist=ARGS_TRADE, parser=trade_cmd) # Add backtesting subcommand backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.', - parents=[_common_parser]) + parents=[_common_parser, _strategy_parser]) backtesting_cmd.set_defaults(func=start_backtesting) self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) # Add edge subcommand edge_cmd = subparsers.add_parser('edge', help='Edge module.', - parents=[_common_parser]) + parents=[_common_parser, _strategy_parser]) edge_cmd.set_defaults(func=start_edge) self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd) # Add hyperopt subcommand hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.', - parents=[_common_parser], + parents=[_common_parser, _strategy_parser], ) hyperopt_cmd.set_defaults(func=start_hyperopt) self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd) @@ -140,7 +144,6 @@ class Arguments: # add create-userdir subcommand create_userdir_cmd = subparsers.add_parser('create-userdir', help="Create user-data directory.", - ) create_userdir_cmd.set_defaults(func=start_create_userdir) self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd) @@ -164,7 +167,7 @@ class Arguments: # Add Plotting subcommand plot_dataframe_cmd = subparsers.add_parser('plot-dataframe', help='Plot candles with indicators.', - parents=[_common_parser], + parents=[_common_parser, _strategy_parser], ) plot_dataframe_cmd.set_defaults(func=start_plot_dataframe) self._build_args(optionlist=ARGS_PLOT_DATAFRAME, parser=plot_dataframe_cmd) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 8a8e31df3..5ff11d5ea 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -198,8 +198,8 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None patch_exchange(mocker) args = [ - '--config', 'config.json', 'hyperopt', + '--config', 'config.json', '--epochs', '5' ] args = get_args(args) From 381b0d3d07f127ac4f1a27b31ccf997947dba103 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2019 16:28:14 +0200 Subject: [PATCH 019/157] Fix typo with new parser --- freqtrade/configuration/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index d2e5d1fc5..a8d4b48f1 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -98,7 +98,7 @@ class Arguments: self._build_args(optionlist=ARGS_COMMON, parser=group) _strategy_parser = argparse.ArgumentParser(add_help=False) - strategy_group = _common_parser.add_argument_group("Strategy arguments") + strategy_group = _strategy_parser.add_argument_group("Strategy arguments") self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group) # Build main command From 2710226326f71eaaaf07830f64b33b905c180224 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2019 19:18:02 +0200 Subject: [PATCH 020/157] Update documentation to use subcommands --- docs/backtesting.md | 2 +- docs/bot-usage.md | 188 ++++++++++++++++++++++++--------- docs/docker.md | 4 +- docs/edge.md | 2 +- docs/faq.md | 2 +- docs/plotting.md | 59 +++++++++-- docs/rest-api.md | 2 +- docs/strategy-customization.md | 6 +- freqtrade.service | 2 +- freqtrade.service.watchdog | 2 +- 10 files changed, 198 insertions(+), 71 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 75aba6c73..6383b1855 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -45,7 +45,7 @@ freqtrade backtesting --datadir user_data/data/bittrex-20180101 #### With a (custom) strategy file ```bash -freqtrade -s SampleStrategy backtesting +freqtrade backtesting -s SampleStrategy ``` Where `-s SampleStrategy` refers to the class name within the strategy file `sample_strategy.py` found in the `freqtrade/user_data/strategies` directory. diff --git a/docs/bot-usage.md b/docs/bot-usage.md index f44400e32..ee01780a0 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -5,27 +5,47 @@ This page explains the different parameters of the bot and how to run it. !!! Note If you've used `setup.sh`, don't forget to activate your virtual environment (`source .env/bin/activate`) before running freqtrade commands. - ## Bot commands ``` -usage: freqtrade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] - [--userdir PATH] [-s NAME] [--strategy-path PATH] - [--db-url PATH] [--sd-notify] - {backtesting,edge,hyperopt,create-userdir,list-exchanges} ... +usage: freqtrade [-h] [-V] + {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,download-data,plot-dataframe,plot-profit} + ... Free, open source crypto trading bot positional arguments: - {backtesting,edge,hyperopt,create-userdir,list-exchanges} + {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,download-data,plot-dataframe,plot-profit} + trade Trade module. backtesting Backtesting module. edge Edge module. hyperopt Hyperopt module. create-userdir Create user-data directory. list-exchanges Print available exchanges. + download-data Download backtesting data. + plot-dataframe Plot candles with indicators. + plot-profit Generate plot showing profits. optional arguments: -h, --help show this help message and exit + -V, --version show program's version number and exit +``` + +### Bot trading commands + +``` +usage: freqtrade trade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] + [--userdir PATH] [-s NAME] [--strategy-path PATH] + [--db-url PATH] [--sd-notify] + +optional arguments: + -h, --help show this help message and exit + --db-url PATH Override trades database URL, this is useful in custom + deployments (default: `sqlite:///tradesv3.sqlite` for + Live Run mode, `sqlite://` for Dry Run). + --sd-notify Notify systemd service manager. + +Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). --logfile FILE Log to the file specified. -V, --version show program's version number and exit @@ -37,14 +57,12 @@ optional arguments: Path to directory with historical backtesting data. --userdir PATH, --user-data-dir PATH Path to userdata directory. + +Strategy arguments: -s NAME, --strategy NAME Specify strategy class name (default: `DefaultStrategy`). --strategy-path PATH Specify additional strategy lookup path. - --db-url PATH Override trades database URL, this is useful in custom - deployments (default: `sqlite:///tradesv3.sqlite` for - Live Run mode, `sqlite://` for Dry Run). - --sd-notify Notify systemd service manager. ``` @@ -128,7 +146,7 @@ In `user_data/strategies` you have a file `my_awesome_strategy.py` which has a strategy class called `AwesomeStrategy` to load it: ```bash -freqtrade --strategy AwesomeStrategy +freqtrade trade --strategy AwesomeStrategy ``` If the bot does not find your strategy file, it will display in an error @@ -143,7 +161,7 @@ This parameter allows you to add an additional strategy lookup path, which gets checked before the default locations (The passed path must be a directory!): ```bash -freqtrade --strategy AwesomeStrategy --strategy-path /some/directory +freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory ``` #### How to install a strategy? @@ -167,20 +185,22 @@ freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite Backtesting also uses the config specified via `-c/--config`. ``` -usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] - [--max_open_trades MAX_OPEN_TRADES] - [--stake_amount STAKE_AMOUNT] [-r] [--eps] [--dmmp] - [-l] - [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] - [--export EXPORT] [--export-filename PATH] +usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [-s NAME] + [--strategy-path PATH] [-i TICKER_INTERVAL] + [--timerange TIMERANGE] [--max_open_trades INT] + [--stake_amount STAKE_AMOUNT] [--eps] [--dmmp] + [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] + [--export EXPORT] [--export-filename PATH] optional arguments: -h, --help show this help message and exit -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL - Specify ticker interval (1m, 5m, 30m, 1h, 1d). + Specify ticker interval (`1m`, `5m`, `30m`, `1h`, + `1d`). --timerange TIMERANGE Specify what timerange of data to use. - --max_open_trades MAX_OPEN_TRADES + --max_open_trades INT Specify max_open_trades to use. --stake_amount STAKE_AMOUNT Specify stake_amount. @@ -193,26 +213,47 @@ optional arguments: number). --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] Provide a space-separated list of strategies to - backtest Please note that ticker-interval needs to be + backtest. Please note that ticker-interval needs to be set either in config or via command line. When using - this together with --export trades, the strategy-name - is injected into the filename (so backtest-data.json - becomes backtest-data-DefaultStrategy.json - --export EXPORT Export backtest results, argument are: trades. Example - --export=trades + this together with `--export trades`, the strategy- + name is injected into the filename (so `backtest- + data.json` becomes `backtest-data- + DefaultStrategy.json` + --export EXPORT Export backtest results, argument are: trades. + Example: `--export=trades` --export-filename PATH - Save backtest results to this filename requires - --export to be set as well Example --export- - filename=user_data/backtest_results/backtest_today.json - (default: user_data/backtest_results/backtest- - result.json) + Save backtest results to the file with this filename + (default: `user_data/backtest_results/backtest- + result.json`). Requires `--export` to be set as well. + Example: `--export-filename=user_data/backtest_results + /backtest_today.json` + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + +Strategy arguments: + -s NAME, --strategy NAME + Specify strategy class name (default: + `DefaultStrategy`). + --strategy-path PATH Specify additional strategy lookup path. + ``` ### Getting historic data for backtesting The first time your run Backtesting, you will need to download some historic data first. This can be accomplished by using `freqtrade download-data`. -Check the corresponding [help page section](backtesting.md#Getting-data-for-backtesting-and-hyperopt) for more details +Check the corresponding [Data Downloading](data-download.md) section for more details ## Hyperopt commands @@ -220,15 +261,17 @@ To optimize your strategy, you can use hyperopt parameter hyperoptimization to find optimal parameter values for your stategy. ``` -usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] +usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] + [--userdir PATH] [-s NAME] [--strategy-path PATH] + [-i TICKER_INTERVAL] [--timerange TIMERANGE] [--max_open_trades INT] - [--stake_amount STAKE_AMOUNT] [-r] + [--stake_amount STAKE_AMOUNT] [--customhyperopt NAME] [--hyperopt-path PATH] [--eps] [-e INT] - [-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] - [--dmmp] [--print-all] [--no-color] [-j JOBS] - [--random-state INT] [--min-trades INT] [--continue] - [--hyperopt-loss NAME] + [--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] + [--dmmp] [--print-all] [--no-color] [--print-json] + [-j JOBS] [--random-state INT] [--min-trades INT] + [--continue] [--hyperopt-loss NAME] optional arguments: -h, --help show this help message and exit @@ -250,7 +293,7 @@ optional arguments: Allow buying the same pair multiple times (position stacking). -e INT, --epochs INT Specify number of epochs (default: 100). - -s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...], --spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...] + --spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...] Specify which parameters to hyperopt. Space-separated list. Default: `all`. --dmmp, --disable-max-market-positions @@ -260,6 +303,7 @@ optional arguments: --print-all Print all results, not only the best ones. --no-color Disable colorization of hyperopt results. May be useful if you are redirecting output to a file. + --print-json Print best result detailization in JSON format. -j JOBS, --job-workers JOBS The number of concurrently running jobs for hyperoptimization (hyperopt worker processes). If -1 @@ -278,8 +322,28 @@ optional arguments: generate completely different results, since the target for optimization is different. Built-in Hyperopt-loss-functions are: DefaultHyperOptLoss, - OnlyProfitHyperOptLoss, SharpeHyperOptLoss. - (default: `DefaultHyperOptLoss`). + OnlyProfitHyperOptLoss, SharpeHyperOptLoss.(default: + `DefaultHyperOptLoss`). + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + +Strategy arguments: + -s NAME, --strategy NAME + Specify strategy class name (default: + `DefaultStrategy`). + --strategy-path PATH Specify additional strategy lookup path. + ``` ## Edge commands @@ -287,26 +351,48 @@ optional arguments: To know your trade expectancy and winrate against historical data, you can use Edge. ``` -usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] - [--max_open_trades MAX_OPEN_TRADES] - [--stake_amount STAKE_AMOUNT] [-r] - [--stoplosses STOPLOSS_RANGE] +usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] + [--userdir PATH] [-s NAME] [--strategy-path PATH] + [-i TICKER_INTERVAL] [--timerange TIMERANGE] + [--max_open_trades INT] [--stake_amount STAKE_AMOUNT] + [--stoplosses STOPLOSS_RANGE] optional arguments: -h, --help show this help message and exit -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL - Specify ticker interval (1m, 5m, 30m, 1h, 1d). + Specify ticker interval (`1m`, `5m`, `30m`, `1h`, + `1d`). --timerange TIMERANGE Specify what timerange of data to use. - --max_open_trades MAX_OPEN_TRADES + --max_open_trades INT Specify max_open_trades to use. --stake_amount STAKE_AMOUNT Specify stake_amount. --stoplosses STOPLOSS_RANGE - Defines a range of stoploss against which edge will - assess the strategy the format is "min,max,step" - (without any space).example: - --stoplosses=-0.01,-0.1,-0.001 + Defines a range of stoploss values against which edge + will assess the strategy. The format is "min,max,step" + (without any space). Example: + `--stoplosses=-0.01,-0.1,-0.001` + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + +Strategy arguments: + -s NAME, --strategy NAME + Specify strategy class name (default: + `DefaultStrategy`). + --strategy-path PATH Specify additional strategy lookup path. + ``` To understand edge and how to read the results, please read the [edge documentation](edge.md). diff --git a/docs/docker.md b/docs/docker.md index 923dec1e2..7fc8d2ba4 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -160,7 +160,7 @@ docker run -d \ -v ~/.freqtrade/config.json:/freqtrade/config.json \ -v ~/.freqtrade/user_data/:/freqtrade/user_data \ -v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \ - freqtrade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy + freqtrade trade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy ``` !!! Note @@ -199,7 +199,7 @@ docker run -d \ -v ~/.freqtrade/config.json:/freqtrade/config.json \ -v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \ -v ~/.freqtrade/user_data/:/freqtrade/user_data/ \ - freqtrade --strategy AwsomelyProfitableStrategy backtesting + freqtrade backtesting --strategy AwsomelyProfitableStrategy ``` Head over to the [Backtesting Documentation](backtesting.md) for more details. diff --git a/docs/edge.md b/docs/edge.md index d91522770..80db7b91e 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -235,7 +235,7 @@ An example of its output: ### Update cached pairs with the latest data Edge requires historic data the same way as backtesting does. -Please refer to the [download section](backtesting.md#Getting-data-for-backtesting-and-hyperopt) of the documentation for details. +Please refer to the [Data Downloading](data-download.md) section of the documentation for details. ### Precising stoploss range diff --git a/docs/faq.md b/docs/faq.md index a441ffacd..c519f8cc3 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -4,7 +4,7 @@ ### The bot does not start -Running the bot with `freqtrade --config config.json` does show the output `freqtrade: command not found`. +Running the bot with `freqtrade trade --config config.json` does show the output `freqtrade: command not found`. This could have the following reasons: diff --git a/docs/plotting.md b/docs/plotting.md index 4deb6db12..25278c99d 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -23,13 +23,15 @@ The `freqtrade plot-dataframe` subcommand shows an interactive graph with three Possible arguments: ``` -usage: freqtrade plot-dataframe [-h] [-p PAIRS [PAIRS ...]] +usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [-s NAME] + [--strategy-path PATH] [-p PAIRS [PAIRS ...]] [--indicators1 INDICATORS1 [INDICATORS1 ...]] [--indicators2 INDICATORS2 [INDICATORS2 ...]] [--plot-limit INT] [--db-url PATH] [--trade-source {DB,file}] [--export EXPORT] [--export-filename PATH] - [--timerange TIMERANGE] + [--timerange TIMERANGE] [-i TICKER_INTERVAL] optional arguments: -h, --help show this help message and exit @@ -62,6 +64,28 @@ optional arguments: /backtest_today.json` --timerange TIMERANGE Specify what timerange of data to use. + -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + Specify ticker interval (`1m`, `5m`, `30m`, `1h`, + `1d`). + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + +Strategy arguments: + -s NAME, --strategy NAME + Specify strategy class name (default: + `DefaultStrategy`). + --strategy-path PATH Specify additional strategy lookup path. ``` @@ -83,7 +107,7 @@ Use `--indicators1` for the main plot and `--indicators2` for the subplot below You will almost certainly want to specify a custom strategy! This can be done by adding `-s Classname` / `--strategy ClassName` to the command. ``` bash -freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --indicators1 sma ema --indicators2 macd +freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH --indicators1 sma ema --indicators2 macd ``` ### Further usage examples @@ -91,25 +115,25 @@ freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --indicators1 sma To plot multiple pairs, separate them with a space: ``` bash -freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH XRP/ETH +freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH XRP/ETH ``` To plot a timerange (to zoom in) ``` bash -freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --timerange=20180801-20180805 +freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH --timerange=20180801-20180805 ``` To plot trades stored in a database use `--db-url` in combination with `--trade-source DB`: ``` bash -freqtrade --strategy AwesomeStrategy plot-dataframe --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB +freqtrade plot-dataframe --strategy AwesomeStrategy --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB ``` To plot trades from a backtesting result, use `--export-filename ` ``` bash -freqtrade --strategy AwesomeStrategy plot-dataframe --export-filename user_data/backtest_results/backtest-result.json -p BTC/ETH +freqtrade plot-dataframe --strategy AwesomeStrategy --export-filename user_data/backtest_results/backtest-result.json -p BTC/ETH ``` ## Plot profit @@ -133,10 +157,11 @@ The third graph can be useful to spot outliers, events in pairs that cause profi Possible options for the `freqtrade plot-profit` subcommand: ``` -usage: freqtrade plot-profit [-h] [-p PAIRS [PAIRS ...]] +usage: freqtrade plot-profit [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] [-p PAIRS [PAIRS ...]] [--timerange TIMERANGE] [--export EXPORT] [--export-filename PATH] [--db-url PATH] - [--trade-source {DB,file}] + [--trade-source {DB,file}] [-i TICKER_INTERVAL] optional arguments: -h, --help show this help message and exit @@ -159,6 +184,22 @@ optional arguments: --trade-source {DB,file} Specify the source for trades (Can be DB or file (backtest file)) Default: file + -i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL + Specify ticker interval (`1m`, `5m`, `30m`, `1h`, + `1d`). + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: `config.json`). + Multiple --config options may be used. Can be set to + `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. ``` diff --git a/docs/rest-api.md b/docs/rest-api.md index afecc1d80..5295ebab4 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -58,7 +58,7 @@ docker run -d \ -v ~/.freqtrade/user_data/:/freqtrade/user_data \ -v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \ -p 127.0.0.1:8080:8080 \ - freqtrade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy + freqtrade trade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy ``` !!! Danger "Security warning" diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index b927e5aad..bb7138759 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -13,7 +13,7 @@ Let assume you have a class called `AwesomeStrategy` in the file `awesome-strate 2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name) ```bash -freqtrade --strategy AwesomeStrategy +freqtrade trade --strategy AwesomeStrategy ``` ## Change your strategy @@ -45,7 +45,7 @@ The current version is 2 - which is also the default when it's not set explicitl Future versions will require this to be set. ```bash -freqtrade --strategy AwesomeStrategy +freqtrade trade --strategy AwesomeStrategy ``` **For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py) @@ -402,7 +402,7 @@ The default buy strategy is located in the file If you want to use a strategy from a different directory you can pass `--strategy-path` ```bash -freqtrade --strategy AwesomeStrategy --strategy-path /some/directory +freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory ``` ### Further strategy ideas diff --git a/freqtrade.service b/freqtrade.service index 9de9f13c7..df220ed39 100644 --- a/freqtrade.service +++ b/freqtrade.service @@ -6,7 +6,7 @@ After=network.target # Set WorkingDirectory and ExecStart to your file paths accordingly # NOTE: %h will be resolved to /home/ WorkingDirectory=%h/freqtrade -ExecStart=/usr/bin/freqtrade +ExecStart=/usr/bin/freqtrade trade Restart=on-failure [Install] diff --git a/freqtrade.service.watchdog b/freqtrade.service.watchdog index ba491fa53..66ea00d76 100644 --- a/freqtrade.service.watchdog +++ b/freqtrade.service.watchdog @@ -6,7 +6,7 @@ After=network.target # Set WorkingDirectory and ExecStart to your file paths accordingly # NOTE: %h will be resolved to /home/ WorkingDirectory=%h/freqtrade -ExecStart=/usr/bin/freqtrade --sd-notify +ExecStart=/usr/bin/freqtrade trade --sd-notify Restart=always #Restart=on-failure From 344a0a094fcaef785d4d58bee58b654561d468dd Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2019 19:21:18 +0200 Subject: [PATCH 021/157] Update remaining documentations --- docs/bot-usage.md | 8 ++++---- docs/hyperopt.md | 2 +- docs/installation.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index ee01780a0..112fc78a1 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -72,7 +72,7 @@ The bot allows you to select which configuration file you want to use by means o the `-c/--config` command line option: ```bash -freqtrade -c path/far/far/away/config.json +freqtrade trade -c path/far/far/away/config.json ``` Per default, the bot loads the `config.json` configuration file from the current @@ -91,13 +91,13 @@ empty key and secrete values while running in the Dry Mode (which does not actua require them): ```bash -freqtrade -c ./config.json +freqtrade trade -c ./config.json ``` and specify both configuration files when running in the normal Live Trade Mode: ```bash -freqtrade -c ./config.json -c path/to/secrets/keys.config.json +freqtrade trade -c ./config.json -c path/to/secrets/keys.config.json ``` This could help you hide your private Exchange key and Exchange secrete on you local machine @@ -177,7 +177,7 @@ using `--db-url`. This can also be used to specify a custom database in production mode. Example command: ```bash -freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite +freqtrade trade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite ``` ## Backtesting commands diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 1ca371e3d..e6f753072 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -239,7 +239,7 @@ Because hyperopt tries a lot of combinations to find the best parameters it will We strongly recommend to use `screen` or `tmux` to prevent any connection loss. ```bash -freqtrade -c config.json hyperopt --customhyperopt -e 5000 --spaces all +freqtrade hyperopt --config config.json --customhyperopt -e 5000 --spaces all ``` Use `` as the name of the custom hyperopt used. diff --git a/docs/installation.md b/docs/installation.md index 68348d4b0..3d0f27f2a 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -187,7 +187,7 @@ python3 -m pip install -e . If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins. ```bash -freqtrade -c config.json +freqtrade trade -c config.json ``` *Note*: If you run the bot on a server, you should consider using [Docker](docker.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout. From 52ff391c8a8eab054287683f82f2beae18703ae6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 29 Sep 2019 19:48:37 +0200 Subject: [PATCH 022/157] Default dockerfile to "freqtrade trade" --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 8677b54de..5b69f55a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,3 +24,5 @@ RUN pip install numpy --no-cache-dir \ COPY . /freqtrade/ RUN pip install -e . --no-cache-dir ENTRYPOINT ["freqtrade"] +# Default to trade mode +CMD [ "trade" ] From b73426b91f5925431a4631c0618fa917d845fe81 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 21 Sep 2019 19:54:44 +0200 Subject: [PATCH 023/157] Disable Defaulting to DefaultStrategy --- .travis.yml | 4 ++-- build_helpers/publish_docker.sh | 2 +- docs/bot-usage.md | 4 ++-- docs/configuration.md | 2 +- freqtrade/configuration/cli_options.py | 3 +-- freqtrade/configuration/configuration.py | 2 +- freqtrade/constants.py | 1 - freqtrade/resolvers/strategy_resolver.py | 7 +++++-- tests/conftest.py | 1 + tests/strategy/test_strategy.py | 12 +++++++++++- tests/test_configuration.py | 1 - 11 files changed, 25 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 81c3de2fb..b066e7044 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,11 +28,11 @@ jobs: name: pytest - script: - cp config.json.example config.json - - freqtrade backtesting --datadir tests/testdata + - freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy name: backtest - script: - cp config.json.example config.json - - freqtrade hyperopt --datadir tests/testdata -e 5 + - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy name: hyperopt - script: flake8 name: flake8 diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh index 839ca0876..b8318c196 100755 --- a/build_helpers/publish_docker.sh +++ b/build_helpers/publish_docker.sh @@ -23,7 +23,7 @@ if [ $? -ne 0 ]; then fi # Run backtest -docker run --rm -it -v $(pwd)/config.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} --datadir /tests/testdata backtesting +docker run --rm -it -v $(pwd)/config.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy DefaultStrategy if [ $? -ne 0 ]; then echo "failed running backtest" diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 112fc78a1..2b66d3c25 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -60,8 +60,8 @@ Common arguments: Strategy arguments: -s NAME, --strategy NAME - Specify strategy class name (default: - `DefaultStrategy`). + Specify strategy class name which will be used by the + bot. --strategy-path PATH Specify additional strategy lookup path. ``` diff --git a/docs/configuration.md b/docs/configuration.md index 0d902766a..9c1b9d4f7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -95,7 +95,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `db_url` | `sqlite:///tradesv3.sqlite`| Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`. | `initial_state` | running | Defines the initial application state. More information below. | `forcebuy_enable` | false | Enables the RPC Commands to force a buy. More information below. -| `strategy` | DefaultStrategy | Defines Strategy class to use. +| `strategy` | None | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`. | `strategy_path` | null | Adds an additional strategy lookup path (must be a directory). | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. | `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 24230f1b3..2ecd4cfc5 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -66,9 +66,8 @@ AVAILABLE_CLI_OPTIONS = { # Main options "strategy": Arg( '-s', '--strategy', - help='Specify strategy class name (default: `%(default)s`).', + help='Specify strategy class name which will be used by the bot.', metavar='NAME', - default='DefaultStrategy', ), "strategy_path": Arg( '--strategy-path', diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 764593d0f..ac27a5c99 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -128,7 +128,7 @@ class Configuration: self._process_logging_options(config) # Set strategy if not specified in config and or if it's non default - if self.args.get("strategy") != constants.DEFAULT_STRATEGY or not config.get('strategy'): + if self.args.get("strategy") or not config.get('strategy'): config.update({'strategy': self.args.get("strategy")}) self._args_to_config(config, argname='strategy_path', diff --git a/freqtrade/constants.py b/freqtrade/constants.py index abf43b24d..749ae25b5 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -9,7 +9,6 @@ PROCESS_THROTTLE_SECS = 5 # sec DEFAULT_TICKER_INTERVAL = 5 # min HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec -DEFAULT_STRATEGY = 'DefaultStrategy' DEFAULT_HYPEROPT = 'DefaultHyperOpts' DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index ca7e1165b..1b6d5c48a 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -32,8 +32,11 @@ class StrategyResolver(IResolver): """ config = config or {} - # Verify the strategy is in the configuration, otherwise fallback to the default strategy - strategy_name = config.get('strategy') or constants.DEFAULT_STRATEGY + if not config.get('strategy'): + raise OperationalException("No strategy set. Please use `--strategy` to specify " + "the strategy class to use.") + + strategy_name = config['strategy'] self.strategy: IStrategy = self._load_strategy(strategy_name, config=config, extra_dir=config.get('strategy_path')) diff --git a/tests/conftest.py b/tests/conftest.py index 6a0a74b5b..0ffb5a066 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -242,6 +242,7 @@ def default_conf(testdatadir): "db_url": "sqlite://", "user_data_dir": Path("user_data"), "verbosity": 3, + "strategy": "DefaultStrategy" } return configuration diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 6992d1aa5..82db30d47 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -55,6 +55,7 @@ def test_load_strategy_base64(result, caplog, default_conf): def test_load_strategy_invalid_directory(result, caplog, default_conf): + default_conf['strategy'] = 'SampleStrategy' resolver = StrategyResolver(default_conf) extra_dir = Path.cwd() / 'some/path' resolver._load_strategy('SampleStrategy', config=default_conf, extra_dir=extra_dir) @@ -65,13 +66,22 @@ def test_load_strategy_invalid_directory(result, caplog, default_conf): def test_load_not_found_strategy(default_conf): - strategy = StrategyResolver(default_conf) + default_conf['strategy'] = 'NotFoundStrategy' with pytest.raises(OperationalException, match=r"Impossible to load Strategy 'NotFoundStrategy'. " r"This class does not exist or contains Python code errors."): + strategy = StrategyResolver(default_conf) strategy._load_strategy(strategy_name='NotFoundStrategy', config=default_conf) +def test_load_strategy_noname(default_conf): + default_conf['strategy'] = '' + with pytest.raises(OperationalException, + match="No strategy set. Please use `--strategy` to specify " + "the strategy class to use."): + StrategyResolver(default_conf) + + def test_strategy(result, default_conf): default_conf.update({'strategy': 'DefaultStrategy'}) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 3c3ad3026..333a8992a 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -212,7 +212,6 @@ def test_load_config(default_conf, mocker) -> None: configuration = Configuration(args) validated_conf = configuration.load_config() - assert validated_conf.get('strategy') == 'DefaultStrategy' assert validated_conf.get('strategy_path') is None assert 'edge' not in validated_conf From 95299d94c4c824eed2c3aca6fc370a8b85633901 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Oct 2019 06:39:24 +0200 Subject: [PATCH 024/157] Remove unused test line --- tests/strategy/test_strategy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 82db30d47..7445e3ca7 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -70,8 +70,7 @@ def test_load_not_found_strategy(default_conf): with pytest.raises(OperationalException, match=r"Impossible to load Strategy 'NotFoundStrategy'. " r"This class does not exist or contains Python code errors."): - strategy = StrategyResolver(default_conf) - strategy._load_strategy(strategy_name='NotFoundStrategy', config=default_conf) + StrategyResolver(default_conf) def test_load_strategy_noname(default_conf): From c4105436ebbad89d37922aad57068bef5c5cca32 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 10 Oct 2019 04:37:32 +0300 Subject: [PATCH 025/157] Disable defaulting to DefaultHyperOpts and DefaultHyperOptLoss --- .travis.yml | 2 +- docs/bot-usage.md | 8 +-- freqtrade/configuration/cli_options.py | 7 +- freqtrade/constants.py | 2 - freqtrade/resolvers/hyperopt_resolver.py | 27 +++++--- tests/optimize/test_hyperopt.py | 81 +++++++++++++++++++++--- 6 files changed, 96 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index b066e7044..7a75a76c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: name: backtest - script: - cp config.json.example config.json - - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy + - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --customhyperopt DefaultHyperOpts --hyperopt-loss DefaultHyperOptLoss name: hyperopt - script: flake8 name: flake8 diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 2b66d3c25..fcf82826a 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -285,8 +285,8 @@ optional arguments: --stake_amount STAKE_AMOUNT Specify stake_amount. --customhyperopt NAME - Specify hyperopt class name (default: - `DefaultHyperOpts`). + Specify hyperopt class name which will be used by the + bot. --hyperopt-path PATH Specify additional lookup path for Hyperopts and Hyperopt Loss functions. --eps, --enable-position-stacking @@ -322,8 +322,8 @@ optional arguments: generate completely different results, since the target for optimization is different. Built-in Hyperopt-loss-functions are: DefaultHyperOptLoss, - OnlyProfitHyperOptLoss, SharpeHyperOptLoss.(default: - `DefaultHyperOptLoss`). + OnlyProfitHyperOptLoss, SharpeHyperOptLoss. + Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 2ecd4cfc5..6928ddfdb 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -153,9 +153,8 @@ AVAILABLE_CLI_OPTIONS = { # Hyperopt "hyperopt": Arg( '--customhyperopt', - help='Specify hyperopt class name (default: `%(default)s`).', + help='Specify hyperopt class name which will be used by the bot.', metavar='NAME', - default=constants.DEFAULT_HYPEROPT, ), "hyperopt_path": Arg( '--hyperopt-path', @@ -232,10 +231,8 @@ AVAILABLE_CLI_OPTIONS = { help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' 'Different functions can generate completely different results, ' 'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' - 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.' - '(default: `%(default)s`).', + 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.', metavar='NAME', - default=constants.DEFAULT_HYPEROPT_LOSS, ), # List exchanges "print_one_column": Arg( diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 749ae25b5..2f490c900 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -9,8 +9,6 @@ PROCESS_THROTTLE_SECS = 5 # sec DEFAULT_TICKER_INTERVAL = 5 # min HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec -DEFAULT_HYPEROPT = 'DefaultHyperOpts' -DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite://' UNLIMITED_STAKE_AMOUNT = 'unlimited' diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index e96394d69..45fe2548e 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -8,7 +8,6 @@ from pathlib import Path from typing import Optional, Dict from freqtrade import OperationalException -from freqtrade.constants import DEFAULT_HYPEROPT, DEFAULT_HYPEROPT_LOSS from freqtrade.optimize.hyperopt_interface import IHyperOpt from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.resolvers import IResolver @@ -20,17 +19,21 @@ class HyperOptResolver(IResolver): """ This class contains all the logic to load custom hyperopt class """ - __slots__ = ['hyperopt'] - def __init__(self, config: Dict) -> None: + def __init__(self, config: Dict = None) -> None: """ Load the custom class from config parameter :param config: configuration dictionary """ + config = config or {} + + if not config.get('hyperopt'): + raise OperationalException("No Hyperopt set. Please use `--customhyperopt` to specify " + "the Hyperopt class to use.") + + hyperopt_name = config['hyperopt'] - # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt - hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT self.hyperopt = self._load_hyperopt(hyperopt_name, config, extra_dir=config.get('hyperopt_path')) @@ -75,7 +78,6 @@ class HyperOptLossResolver(IResolver): """ This class contains all the logic to load custom hyperopt loss class """ - __slots__ = ['hyperoptloss'] def __init__(self, config: Dict = None) -> None: @@ -85,17 +87,22 @@ class HyperOptLossResolver(IResolver): """ config = config or {} - # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt - hyperopt_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS + if not config.get('hyperopt_loss'): + raise OperationalException("No Hyperopt Loss Function set. Please use " + "`--hyperopt-loss` to specify " + "the Hyperopt Loss Function class to use.") + + hyperoptloss_name = config['hyperopt_loss'] + self.hyperoptloss = self._load_hyperoptloss( - hyperopt_name, config, extra_dir=config.get('hyperopt_path')) + hyperoptloss_name, config, extra_dir=config.get('hyperopt_path')) # Assign ticker_interval to be used in hyperopt self.hyperoptloss.__class__.ticker_interval = str(config['ticker_interval']) if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'): raise OperationalException( - f"Found hyperopt {hyperopt_name} does not implement `hyperopt_loss_function`.") + f"Found hyperopt {hyperoptloss_name} does not implement `hyperopt_loss_function`.") def _load_hyperoptloss( self, hyper_loss_name: str, config: Dict, diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 5ff11d5ea..cf211e35b 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -25,7 +25,11 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, @pytest.fixture(scope='function') def hyperopt(default_conf, mocker): - default_conf.update({'spaces': ['all']}) + default_conf.update({ + 'spaces': ['all'], + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', + }) patch_exchange(mocker) return Hyperopt(default_conf) @@ -70,6 +74,8 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt-loss', 'DefaultHyperOptLoss', ] config = setup_configuration(get_args(args), RunMode.HYPEROPT) @@ -101,6 +107,8 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt-loss', 'DefaultHyperOptLoss', '--datadir', '/foo/bar', '--ticker-interval', '1m', '--timerange', ':100', @@ -155,7 +163,9 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: 'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt', MagicMock(return_value=hyperopts(default_conf)) ) - x = HyperOptResolver(default_conf, ).hyperopt + default_conf.update({'hyperopt': 'DefaultHyperOpts'}) + default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) + x = HyperOptResolver(default_conf).hyperopt assert not hasattr(x, 'populate_buy_trend') assert not hasattr(x, 'populate_sell_trend') assert log_has("Hyperopt class does not provide populate_sell_trend() method. " @@ -169,7 +179,15 @@ def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None: default_conf.update({'hyperopt': "NonExistingHyperoptClass"}) with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'): - HyperOptResolver(default_conf, ).hyperopt + HyperOptResolver(default_conf).hyperopt + + +def test_hyperoptresolver_noname(default_conf): + default_conf['hyperopt'] = '' + with pytest.raises(OperationalException, + match="No Hyperopt set. Please use `--customhyperopt` to specify " + "the Hyperopt class to use."): + HyperOptResolver(default_conf) def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None: @@ -179,7 +197,8 @@ def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None: 'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss', MagicMock(return_value=hl) ) - x = HyperOptLossResolver(default_conf, ).hyperoptloss + default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) + x = HyperOptLossResolver(default_conf).hyperoptloss assert hasattr(x, "hyperopt_loss_function") @@ -187,7 +206,17 @@ def test_hyperoptlossresolver_wrongname(mocker, default_conf, caplog) -> None: default_conf.update({'hyperopt_loss': "NonExistingLossClass"}) with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'): - HyperOptLossResolver(default_conf, ).hyperopt + HyperOptLossResolver(default_conf).hyperopt + + +def test_hyperoptlossresolver_noname(default_conf): + default_conf.update({'hyperopt': 'DefaultHyperOpts'}) + default_conf['hyperopt_loss'] = '' + with pytest.raises(OperationalException, + match="No Hyperopt Loss Function set. Please use " + "`--hyperopt-loss` to specify " + "the Hyperopt Loss Function class to use."): + HyperOptLossResolver(default_conf) def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None: @@ -200,6 +229,8 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -217,6 +248,8 @@ def test_start(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -239,6 +272,8 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -256,6 +291,8 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -264,6 +301,7 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None: + default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) hl = HyperOptLossResolver(default_conf).hyperoptloss correct = hl.hyperopt_loss_function(hyperopt_results, 600) over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100) @@ -276,6 +314,7 @@ def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results) resultsb = hyperopt_results.copy() resultsb.loc[1, 'trade_duration'] = 20 + default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) hl = HyperOptLossResolver(default_conf).hyperoptloss longer = hl.hyperopt_loss_function(hyperopt_results, 100) shorter = hl.hyperopt_loss_function(resultsb, 100) @@ -288,6 +327,7 @@ def test_loss_calculation_has_limited_profit(default_conf, hyperopt_results) -> results_under = hyperopt_results.copy() results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2 + default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) hl = HyperOptLossResolver(default_conf).hyperoptloss correct = hl.hyperopt_loss_function(hyperopt_results, 600) over = hl.hyperopt_loss_function(results_over, 600) @@ -407,6 +447,8 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -510,10 +552,13 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None: def test_generate_optimizer(mocker, default_conf) -> None: - default_conf.update({'config': 'config.json.example'}) - default_conf.update({'timerange': None}) - default_conf.update({'spaces': 'all'}) - default_conf.update({'hyperopt_min_trades': 1}) + default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', + 'timerange': None, + 'spaces': 'all', + 'hyperopt_min_trades': 1, + }) trades = [ ('POWR/BTC', 0.023117, 0.000233, 100) @@ -576,6 +621,8 @@ def test_generate_optimizer(mocker, default_conf) -> None: def test_clean_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -592,6 +639,8 @@ def test_clean_hyperopt(mocker, default_conf, caplog): def test_continue_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -621,6 +670,8 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -658,6 +709,8 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -696,6 +749,8 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -737,6 +792,8 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) - patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -770,6 +827,8 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'buy', @@ -815,6 +874,8 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'sell', @@ -862,6 +923,8 @@ def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, metho patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': space, From 08e6d8a7809222c217ba7050a9114dbab385d1f2 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 11 Oct 2019 23:33:22 +0300 Subject: [PATCH 026/157] Rollback defaulting to DefaultHyperOptLoss --- .travis.yml | 2 +- docs/bot-usage.md | 3 ++- freqtrade/configuration/cli_options.py | 4 ++- freqtrade/constants.py | 1 + freqtrade/resolvers/hyperopt_resolver.py | 10 +++---- tests/optimize/test_hyperopt.py | 33 ------------------------ 6 files changed, 11 insertions(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7a75a76c2..a45334dd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: name: backtest - script: - cp config.json.example config.json - - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --customhyperopt DefaultHyperOpts --hyperopt-loss DefaultHyperOptLoss + - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --customhyperopt DefaultHyperOpts name: hyperopt - script: flake8 name: flake8 diff --git a/docs/bot-usage.md b/docs/bot-usage.md index fcf82826a..8f7e0bbcf 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -322,7 +322,8 @@ optional arguments: generate completely different results, since the target for optimization is different. Built-in Hyperopt-loss-functions are: DefaultHyperOptLoss, - OnlyProfitHyperOptLoss, SharpeHyperOptLoss. + OnlyProfitHyperOptLoss, SharpeHyperOptLoss (default: + `DefaultHyperOptLoss`). Common arguments: diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 6928ddfdb..ee0d94023 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -231,8 +231,10 @@ AVAILABLE_CLI_OPTIONS = { help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' 'Different functions can generate completely different results, ' 'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' - 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.', + 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss ' + '(default: `%(default)s`).', metavar='NAME', + default=constants.DEFAULT_HYPEROPT_LOSS, ), # List exchanges "print_one_column": Arg( diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 2f490c900..b053519b0 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -9,6 +9,7 @@ PROCESS_THROTTLE_SECS = 5 # sec DEFAULT_TICKER_INTERVAL = 5 # min HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec +DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite://' UNLIMITED_STAKE_AMOUNT = 'unlimited' diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 45fe2548e..d1bc90e13 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Optional, Dict from freqtrade import OperationalException +from freqtrade.constants import DEFAULT_HYPEROPT_LOSS from freqtrade.optimize.hyperopt_interface import IHyperOpt from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.resolvers import IResolver @@ -87,12 +88,9 @@ class HyperOptLossResolver(IResolver): """ config = config or {} - if not config.get('hyperopt_loss'): - raise OperationalException("No Hyperopt Loss Function set. Please use " - "`--hyperopt-loss` to specify " - "the Hyperopt Loss Function class to use.") - - hyperoptloss_name = config['hyperopt_loss'] + # Verify the hyperopt_loss is in the configuration, otherwise fallback to the + # default hyperopt loss + hyperoptloss_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS self.hyperoptloss = self._load_hyperoptloss( hyperoptloss_name, config, extra_dir=config.get('hyperopt_path')) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index cf211e35b..e1ee649c8 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -28,7 +28,6 @@ def hyperopt(default_conf, mocker): default_conf.update({ 'spaces': ['all'], 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', }) patch_exchange(mocker) return Hyperopt(default_conf) @@ -75,7 +74,6 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca 'hyperopt', '--config', 'config.json', '--customhyperopt', 'DefaultHyperOpts', - '--hyperopt-loss', 'DefaultHyperOptLoss', ] config = setup_configuration(get_args(args), RunMode.HYPEROPT) @@ -108,7 +106,6 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo 'hyperopt', '--config', 'config.json', '--customhyperopt', 'DefaultHyperOpts', - '--hyperopt-loss', 'DefaultHyperOptLoss', '--datadir', '/foo/bar', '--ticker-interval', '1m', '--timerange', ':100', @@ -164,7 +161,6 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: MagicMock(return_value=hyperopts(default_conf)) ) default_conf.update({'hyperopt': 'DefaultHyperOpts'}) - default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) x = HyperOptResolver(default_conf).hyperopt assert not hasattr(x, 'populate_buy_trend') assert not hasattr(x, 'populate_sell_trend') @@ -197,7 +193,6 @@ def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None: 'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss', MagicMock(return_value=hl) ) - default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) x = HyperOptLossResolver(default_conf).hyperoptloss assert hasattr(x, "hyperopt_loss_function") @@ -209,16 +204,6 @@ def test_hyperoptlossresolver_wrongname(mocker, default_conf, caplog) -> None: HyperOptLossResolver(default_conf).hyperopt -def test_hyperoptlossresolver_noname(default_conf): - default_conf.update({'hyperopt': 'DefaultHyperOpts'}) - default_conf['hyperopt_loss'] = '' - with pytest.raises(OperationalException, - match="No Hyperopt Loss Function set. Please use " - "`--hyperopt-loss` to specify " - "the Hyperopt Loss Function class to use."): - HyperOptLossResolver(default_conf) - - def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None: start_mock = MagicMock() patched_configuration_load_config_file(mocker, default_conf) @@ -230,7 +215,6 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None 'hyperopt', '--config', 'config.json', '--customhyperopt', 'DefaultHyperOpts', - '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -249,7 +233,6 @@ def test_start(mocker, default_conf, caplog) -> None: 'hyperopt', '--config', 'config.json', '--customhyperopt', 'DefaultHyperOpts', - '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -273,7 +256,6 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: 'hyperopt', '--config', 'config.json', '--customhyperopt', 'DefaultHyperOpts', - '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -292,7 +274,6 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: 'hyperopt', '--config', 'config.json', '--customhyperopt', 'DefaultHyperOpts', - '--hyperopt-loss', 'DefaultHyperOptLoss', '--epochs', '5' ] args = get_args(args) @@ -301,7 +282,6 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None: - default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) hl = HyperOptLossResolver(default_conf).hyperoptloss correct = hl.hyperopt_loss_function(hyperopt_results, 600) over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100) @@ -314,7 +294,6 @@ def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results) resultsb = hyperopt_results.copy() resultsb.loc[1, 'trade_duration'] = 20 - default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) hl = HyperOptLossResolver(default_conf).hyperoptloss longer = hl.hyperopt_loss_function(hyperopt_results, 100) shorter = hl.hyperopt_loss_function(resultsb, 100) @@ -327,7 +306,6 @@ def test_loss_calculation_has_limited_profit(default_conf, hyperopt_results) -> results_under = hyperopt_results.copy() results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2 - default_conf.update({'hyperopt_loss': 'DefaultHyperOptLoss'}) hl = HyperOptLossResolver(default_conf).hyperoptloss correct = hl.hyperopt_loss_function(hyperopt_results, 600) over = hl.hyperopt_loss_function(results_over, 600) @@ -448,7 +426,6 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -554,7 +531,6 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None: def test_generate_optimizer(mocker, default_conf) -> None: default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'timerange': None, 'spaces': 'all', 'hyperopt_min_trades': 1, @@ -622,7 +598,6 @@ def test_clean_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -640,7 +615,6 @@ def test_continue_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -671,7 +645,6 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -710,7 +683,6 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -750,7 +722,6 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -793,7 +764,6 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) - default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -828,7 +798,6 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None: default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'buy', @@ -875,7 +844,6 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': 'sell', @@ -924,7 +892,6 @@ def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, metho default_conf.update({'config': 'config.json.example', 'hyperopt': 'DefaultHyperOpts', - 'hyperopt_loss': 'DefaultHyperOptLoss', 'epochs': 1, 'timerange': None, 'spaces': space, From ff1fa17dc3243cc3a9c1469ef92b542c49e86d47 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sun, 13 Oct 2019 03:41:25 +0300 Subject: [PATCH 027/157] No default value for the config parameter --- freqtrade/resolvers/hyperopt_resolver.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index d1bc90e13..15080cda5 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -22,13 +22,11 @@ class HyperOptResolver(IResolver): """ __slots__ = ['hyperopt'] - def __init__(self, config: Dict = None) -> None: + def __init__(self, config: Dict) -> None: """ Load the custom class from config parameter :param config: configuration dictionary """ - config = config or {} - if not config.get('hyperopt'): raise OperationalException("No Hyperopt set. Please use `--customhyperopt` to specify " "the Hyperopt class to use.") @@ -81,12 +79,11 @@ class HyperOptLossResolver(IResolver): """ __slots__ = ['hyperoptloss'] - def __init__(self, config: Dict = None) -> None: + def __init__(self, config: Dict) -> None: """ Load the custom class from config parameter - :param config: configuration dictionary or None + :param config: configuration dictionary """ - config = config or {} # Verify the hyperopt_loss is in the configuration, otherwise fallback to the # default hyperopt loss @@ -100,7 +97,8 @@ class HyperOptLossResolver(IResolver): if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'): raise OperationalException( - f"Found hyperopt {hyperoptloss_name} does not implement `hyperopt_loss_function`.") + f"Found HyperoptLoss class {hyperoptloss_name} does not " + "implement `hyperopt_loss_function`.") def _load_hyperoptloss( self, hyper_loss_name: str, config: Dict, From 89283ef486ad48a03f0ee694a1784be1b279702b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Oct 2019 19:42:28 +0200 Subject: [PATCH 028/157] Rename --custom-hyperopt to --hyperopt --- .travis.yml | 2 +- docs/bot-usage.md | 4 ++-- docs/hyperopt.md | 2 +- freqtrade/configuration/cli_options.py | 2 +- freqtrade/resolvers/hyperopt_resolver.py | 2 +- tests/optimize/test_hyperopt.py | 14 +++++++------- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index a45334dd6..14466d2c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: name: backtest - script: - cp config.json.example config.json - - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --customhyperopt DefaultHyperOpts + - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --hyperopt DefaultHyperOpts name: hyperopt - script: flake8 name: flake8 diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 8f7e0bbcf..cf59bc11f 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -266,7 +266,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [--max_open_trades INT] [--stake_amount STAKE_AMOUNT] - [--customhyperopt NAME] [--hyperopt-path PATH] + [--hyperopt NAME] [--hyperopt-path PATH] [--eps] [-e INT] [--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] [--dmmp] [--print-all] [--no-color] [--print-json] @@ -284,7 +284,7 @@ optional arguments: Specify max_open_trades to use. --stake_amount STAKE_AMOUNT Specify stake_amount. - --customhyperopt NAME + --hyperopt NAME Specify hyperopt class name which will be used by the bot. --hyperopt-path PATH Specify additional lookup path for Hyperopts and diff --git a/docs/hyperopt.md b/docs/hyperopt.md index e6f753072..66c250eb7 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -239,7 +239,7 @@ Because hyperopt tries a lot of combinations to find the best parameters it will We strongly recommend to use `screen` or `tmux` to prevent any connection loss. ```bash -freqtrade hyperopt --config config.json --customhyperopt -e 5000 --spaces all +freqtrade hyperopt --config config.json --hyperopt -e 5000 --spaces all ``` Use `` as the name of the custom hyperopt used. diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index ee0d94023..3a4629ada 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -152,7 +152,7 @@ AVAILABLE_CLI_OPTIONS = { ), # Hyperopt "hyperopt": Arg( - '--customhyperopt', + '--hyperopt', help='Specify hyperopt class name which will be used by the bot.', metavar='NAME', ), diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 15080cda5..a51935500 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -28,7 +28,7 @@ class HyperOptResolver(IResolver): :param config: configuration dictionary """ if not config.get('hyperopt'): - raise OperationalException("No Hyperopt set. Please use `--customhyperopt` to specify " + raise OperationalException("No Hyperopt set. Please use `--hyperopt` to specify " "the Hyperopt class to use.") hyperopt_name = config['hyperopt'] diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index e1ee649c8..1c89bb37c 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -73,7 +73,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca args = [ 'hyperopt', '--config', 'config.json', - '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpts', ] config = setup_configuration(get_args(args), RunMode.HYPEROPT) @@ -105,7 +105,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo args = [ 'hyperopt', '--config', 'config.json', - '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpts', '--datadir', '/foo/bar', '--ticker-interval', '1m', '--timerange', ':100', @@ -181,7 +181,7 @@ def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None: def test_hyperoptresolver_noname(default_conf): default_conf['hyperopt'] = '' with pytest.raises(OperationalException, - match="No Hyperopt set. Please use `--customhyperopt` to specify " + match="No Hyperopt set. Please use `--hyperopt` to specify " "the Hyperopt class to use."): HyperOptResolver(default_conf) @@ -214,7 +214,7 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None args = [ 'hyperopt', '--config', 'config.json', - '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) @@ -232,7 +232,7 @@ def test_start(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) @@ -255,7 +255,7 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) @@ -273,7 +273,7 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--customhyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) From a5c83b66df0274e126be59d997d5aae210589159 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 15 Oct 2019 06:51:03 +0200 Subject: [PATCH 029/157] Add --dry-run to trade command --- docs/bot-usage.md | 5 +++-- freqtrade/configuration/arguments.py | 2 +- freqtrade/configuration/cli_options.py | 5 +++++ freqtrade/configuration/configuration.py | 4 ++++ tests/test_configuration.py | 17 +++++++++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 8f7e0bbcf..a258ee7f5 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -36,7 +36,7 @@ optional arguments: ``` usage: freqtrade trade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH] - [--db-url PATH] [--sd-notify] + [--db-url PATH] [--sd-notify] [--dry-run] optional arguments: -h, --help show this help message and exit @@ -44,6 +44,8 @@ optional arguments: deployments (default: `sqlite:///tradesv3.sqlite` for Live Run mode, `sqlite://` for Dry Run). --sd-notify Notify systemd service manager. + --dry-run Enforce dry-run for trading, removes API keys and + simulates trades. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -63,7 +65,6 @@ Strategy arguments: Specify strategy class name which will be used by the bot. --strategy-path PATH Specify additional strategy lookup path. - ``` ### How to specify which configuration file be used? diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index a8d4b48f1..5735599df 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -12,7 +12,7 @@ ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_dat ARGS_STRATEGY = ["strategy", "strategy_path"] -ARGS_TRADE = ["db_url", "sd_notify"] +ARGS_TRADE = ["db_url", "sd_notify", "dry_run"] ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange", "max_open_trades", "stake_amount"] diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index ee0d94023..400d08e37 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -86,6 +86,11 @@ AVAILABLE_CLI_OPTIONS = { help='Notify systemd service manager.', action='store_true', ), + "dry_run": Arg( + '--dry-run', + help='Enforce dry-run for trading, removes API keys and simulates trades.', + action='store_true', + ), # Optimize common "ticker_interval": Arg( '-i', '--ticker-interval', diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index ac27a5c99..7e992d6dd 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -162,6 +162,10 @@ class Configuration: if 'sd_notify' in self.args and self.args["sd_notify"]: config['internals'].update({'sd_notify': True}) + self._args_to_config(config, argname='dry_run', + logstring='Parameter --dry-run detected, ' + 'overriding dry_run to: {} ...') + def _process_datadir_options(self, config: Dict[str, Any]) -> None: """ Extract information for sys.argv and load directory configurations diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 333a8992a..67c97a0bf 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -304,6 +304,23 @@ def test_load_config_with_params(default_conf, mocker) -> None: assert validated_conf.get('db_url') == DEFAULT_DB_DRYRUN_URL +@pytest.mark.parametrize("config_value,expected,arglist", [ + (True, True, ['trade', '--dry-run']), # Leave config untouched + (False, True, ['trade', '--dry-run']), # Override config untouched + (False, False, ['trade']), # Leave config untouched + (True, True, ['trade']), # Leave config untouched +]) +def test_load_dry_run(default_conf, mocker, config_value, expected, arglist) -> None: + + default_conf['dry_run'] = config_value + patched_configuration_load_config_file(mocker, default_conf) + + configuration = Configuration(Arguments(arglist).get_parsed_arg()) + validated_conf = configuration.load_config() + + assert validated_conf.get('dry_run') is expected + + def test_load_custom_strategy(default_conf, mocker) -> None: default_conf.update({ 'strategy': 'CustomStrategy', From 6fb96183c08342343604cb5c93296f812a4849b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 15 Oct 2019 12:26:06 +0200 Subject: [PATCH 030/157] Reword help string --- docs/bot-usage.md | 4 ++-- freqtrade/configuration/cli_options.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index a258ee7f5..9af960858 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -44,8 +44,8 @@ optional arguments: deployments (default: `sqlite:///tradesv3.sqlite` for Live Run mode, `sqlite://` for Dry Run). --sd-notify Notify systemd service manager. - --dry-run Enforce dry-run for trading, removes API keys and - simulates trades. + --dry-run Enforce dry-run for trading (removes Exchange secrets + and simulates trades). Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 400d08e37..b3dcd52c2 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -88,7 +88,7 @@ AVAILABLE_CLI_OPTIONS = { ), "dry_run": Arg( '--dry-run', - help='Enforce dry-run for trading, removes API keys and simulates trades.', + help='Enforce dry-run for trading (removes Exchange secrets and simulates trades).', action='store_true', ), # Optimize common From 2d34c0f52de724e87f728ca1c749128eb3cfb253 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Oct 2019 19:35:38 +0200 Subject: [PATCH 031/157] Update helpstring exports --- docs/bot-usage.md | 47 +++++++++++++------------- freqtrade/configuration/cli_options.py | 2 +- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index d2b9757f5..8c85965a4 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -9,19 +9,21 @@ This page explains the different parameters of the bot and how to run it. ``` usage: freqtrade [-h] [-V] - {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,download-data,plot-dataframe,plot-profit} + {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} ... Free, open source crypto trading bot positional arguments: - {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,download-data,plot-dataframe,plot-profit} + {trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit} trade Trade module. backtesting Backtesting module. edge Edge module. hyperopt Hyperopt module. create-userdir Create user-data directory. list-exchanges Print available exchanges. + list-timeframes Print available ticker intervals (timeframes) for the + exchange. download-data Download backtesting data. plot-dataframe Plot candles with indicators. plot-profit Generate plot showing profits. @@ -29,6 +31,7 @@ positional arguments: optional arguments: -h, --help show this help message and exit -V, --version show program's version number and exit + ``` ### Bot trading commands @@ -190,7 +193,8 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [--max_open_trades INT] - [--stake_amount STAKE_AMOUNT] [--eps] [--dmmp] + [--stake_amount STAKE_AMOUNT] [--fee FLOAT] + [--eps] [--dmmp] [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] [--export EXPORT] [--export-filename PATH] @@ -225,11 +229,10 @@ optional arguments: --export EXPORT Export backtest results, argument are: trades. Example: `--export=trades` --export-filename PATH - Save backtest results to the file with this filename - (default: `user_data/backtest_results/backtest- - result.json`). Requires `--export` to be set as well. - Example: `--export-filename=user_data/backtest_results - /backtest_today.json` + Save backtest results to the file with this filename. + Requires `--export` to be set as well. Example: + `--export-filename=user_data/backtest_results/backtest + _today.json` Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -246,8 +249,8 @@ Common arguments: Strategy arguments: -s NAME, --strategy NAME - Specify strategy class name (default: - `DefaultStrategy`). + Specify strategy class name which will be used by the + bot. --strategy-path PATH Specify additional strategy lookup path. ``` @@ -268,9 +271,9 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [--max_open_trades INT] - [--stake_amount STAKE_AMOUNT] - [--hyperopt NAME] [--hyperopt-path PATH] - [--eps] [-e INT] + [--stake_amount STAKE_AMOUNT] [--fee FLOAT] + [--hyperopt NAME] [--hyperopt-path PATH] [--eps] + [-e INT] [--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] [--dmmp] [--print-all] [--no-color] [--print-json] [-j JOBS] [--random-state INT] [--min-trades INT] @@ -287,8 +290,9 @@ optional arguments: Specify max_open_trades to use. --stake_amount STAKE_AMOUNT Specify stake_amount. - --hyperopt NAME - Specify hyperopt class name which will be used by the + --fee FLOAT Specify fee ratio. Will be applied twice (on trade + entry and exit). + --hyperopt NAME Specify hyperopt class name which will be used by the bot. --hyperopt-path PATH Specify additional lookup path for Hyperopts and Hyperopt Loss functions. @@ -328,7 +332,6 @@ optional arguments: OnlyProfitHyperOptLoss, SharpeHyperOptLoss (default: `DefaultHyperOptLoss`). - Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). --logfile FILE Log to the file specified. @@ -344,10 +347,9 @@ Common arguments: Strategy arguments: -s NAME, --strategy NAME - Specify strategy class name (default: - `DefaultStrategy`). + Specify strategy class name which will be used by the + bot. --strategy-path PATH Specify additional strategy lookup path. - ``` ## Edge commands @@ -359,7 +361,7 @@ usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [--max_open_trades INT] [--stake_amount STAKE_AMOUNT] - [--stoplosses STOPLOSS_RANGE] + [--fee FLOAT] [--stoplosses STOPLOSS_RANGE] optional arguments: -h, --help show this help message and exit @@ -395,10 +397,9 @@ Common arguments: Strategy arguments: -s NAME, --strategy NAME - Specify strategy class name (default: - `DefaultStrategy`). + Specify strategy class name which will be used by the + bot. --strategy-path PATH Specify additional strategy lookup path. - ``` To understand edge and how to read the results, please read the [edge documentation](edge.md). diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index a62de10eb..ac72cff0b 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -140,7 +140,7 @@ AVAILABLE_CLI_OPTIONS = { ), "exportfilename": Arg( '--export-filename', - help='Save backtest results to the file with this filename (default: `%(default)s`). ' + help='Save backtest results to the file with this filename. ' 'Requires `--export` to be set as well. ' 'Example: `--export-filename=user_data/backtest_results/backtest_today.json`', metavar='PATH', From 1c503f39b2226ae19133a7c03131cede6905f03b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Oct 2019 19:54:38 +0200 Subject: [PATCH 032/157] Handle some merge aftermaths --- .travis.yml | 2 +- freqtrade/configuration/arguments.py | 39 ++++++++++++++++------------ tests/optimize/test_hyperopt.py | 38 +++++++++++++-------------- tests/strategy/test_strategy.py | 2 +- tests/test_arguments.py | 4 +-- tests/test_utils.py | 4 +-- 6 files changed, 47 insertions(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index 14466d2c4..d5d4ad8c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: name: backtest - script: - cp config.json.example config.json - - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --hyperopt DefaultHyperOpts + - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --hyperopt DefaultHyperOpt name: hyperopt - script: flake8 name: flake8 diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index b093a5015..bc58fc8e8 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -153,41 +153,46 @@ class Arguments: self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd) # Add list-exchanges subcommand - list_exchanges_cmd = subparsers.add_parser('list-exchanges', - help='Print available exchanges.', - parents=[_common_parser], - ) + list_exchanges_cmd = subparsers.add_parser( + 'list-exchanges', + help='Print available exchanges.', + parents=[_common_parser], + ) list_exchanges_cmd.set_defaults(func=start_list_exchanges) self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd) # Add list-timeframes subcommand list_timeframes_cmd = subparsers.add_parser( 'list-timeframes', - help='Print available ticker intervals (timeframes) for the exchange.' + help='Print available ticker intervals (timeframes) for the exchange.', + parents=[_common_parser], ) list_timeframes_cmd.set_defaults(func=start_list_timeframes) self._build_args(optionlist=ARGS_LIST_TIMEFRAMES, parser=list_timeframes_cmd) # Add download-data subcommand - download_data_cmd = subparsers.add_parser('download-data', - help='Download backtesting data.', - parents=[_common_parser], - ) + download_data_cmd = subparsers.add_parser( + 'download-data', + help='Download backtesting data.', + parents=[_common_parser], + ) download_data_cmd.set_defaults(func=start_download_data) self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd) # Add Plotting subcommand - plot_dataframe_cmd = subparsers.add_parser('plot-dataframe', - help='Plot candles with indicators.', - parents=[_common_parser, _strategy_parser], - ) + plot_dataframe_cmd = subparsers.add_parser( + 'plot-dataframe', + help='Plot candles with indicators.', + parents=[_common_parser, _strategy_parser], + ) plot_dataframe_cmd.set_defaults(func=start_plot_dataframe) self._build_args(optionlist=ARGS_PLOT_DATAFRAME, parser=plot_dataframe_cmd) # Plot profit - plot_profit_cmd = subparsers.add_parser('plot-profit', - help='Generate plot showing profits.', - parents=[_common_parser], - ) + plot_profit_cmd = subparsers.add_parser( + 'plot-profit', + help='Generate plot showing profits.', + parents=[_common_parser], + ) plot_profit_cmd.set_defaults(func=start_plot_profit) self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index c9ecb63dc..6bed0f8cc 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -27,7 +27,7 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, def hyperopt(default_conf, mocker): default_conf.update({ 'spaces': ['all'], - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', }) patch_exchange(mocker) return Hyperopt(default_conf) @@ -73,7 +73,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpt', ] config = setup_configuration(get_args(args), RunMode.HYPEROPT) @@ -105,7 +105,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpt', '--datadir', '/foo/bar', '--ticker-interval', '1m', '--timerange', ':100', @@ -160,7 +160,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: 'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt', MagicMock(return_value=hyperopt(default_conf)) ) - default_conf.update({'hyperopt': 'DefaultHyperOpts'}) + default_conf.update({'hyperopt': 'DefaultHyperOpt'}) x = HyperOptResolver(default_conf).hyperopt assert not hasattr(x, 'populate_buy_trend') assert not hasattr(x, 'populate_sell_trend') @@ -214,7 +214,7 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] args = get_args(args) @@ -232,7 +232,7 @@ def test_start(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] args = get_args(args) @@ -255,7 +255,7 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] args = get_args(args) @@ -273,7 +273,7 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', - '--hyperopt', 'DefaultHyperOpts', + '--hyperopt', 'DefaultHyperOpt', '--epochs', '5' ] args = get_args(args) @@ -425,7 +425,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -530,7 +530,7 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None: def test_generate_optimizer(mocker, default_conf) -> None: default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'timerange': None, 'spaces': 'all', 'hyperopt_min_trades': 1, @@ -597,7 +597,7 @@ def test_generate_optimizer(mocker, default_conf) -> None: def test_clean_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -614,7 +614,7 @@ def test_clean_hyperopt(mocker, default_conf, caplog): def test_continue_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -644,7 +644,7 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -682,7 +682,7 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -721,7 +721,7 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -763,7 +763,7 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) - patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -797,7 +797,7 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'buy', @@ -843,7 +843,7 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': 'sell', @@ -891,7 +891,7 @@ def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, metho patch_exchange(mocker) default_conf.update({'config': 'config.json.example', - 'hyperopt': 'DefaultHyperOpts', + 'hyperopt': 'DefaultHyperOpt', 'epochs': 1, 'timerange': None, 'spaces': space, diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 285ac1bbf..97affc99c 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -61,7 +61,7 @@ def test_load_strategy_invalid_directory(result, caplog, default_conf): assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog) - assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) + assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) def test_load_not_found_strategy(default_conf): diff --git a/tests/test_arguments.py b/tests/test_arguments.py index be711aeda..d8fbace0f 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -186,7 +186,7 @@ def test_config_notallowed(mocker) -> None: ] pargs = Arguments(args).get_parsed_arg() - assert pargs["config"] is None + assert "config" not in pargs # When file exists: mocker.patch.object(Path, "is_file", MagicMock(return_value=True)) @@ -195,7 +195,7 @@ def test_config_notallowed(mocker) -> None: ] pargs = Arguments(args).get_parsed_arg() # config is not added even if it exists, since create-userdir is in the notallowed list - assert pargs["config"] is None + assert "config" not in pargs def test_config_notrequired(mocker) -> None: diff --git a/tests/test_utils.py b/tests/test_utils.py index f798dcfb8..4e76bb6ca 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -95,8 +95,8 @@ def test_list_timeframes(mocker, capsys): # Test with --config config.json.example args = [ - '--config', 'config.json.example', "list-timeframes", + '--config', 'config.json.example', ] start_list_timeframes(get_args(args)) captured = capsys.readouterr() @@ -139,8 +139,8 @@ def test_list_timeframes(mocker, capsys): # Test with --one-column args = [ - '--config', 'config.json.example', "list-timeframes", + '--config', 'config.json.example', "--one-column", ] start_list_timeframes(get_args(args)) From e1edf363076b9b6397e0f2d78a7323f922fb5989 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Oct 2019 06:22:05 +0200 Subject: [PATCH 033/157] Fix test failures --- freqtrade/configuration/arguments.py | 6 +++-- tests/test_utils.py | 34 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index e62921af8..29d0d98a2 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -179,7 +179,8 @@ class Arguments: # Add list-markets subcommand list_markets_cmd = subparsers.add_parser( 'list-markets', - help='Print markets on exchange.' + help='Print markets on exchange.', + parents=[_common_parser], ) list_markets_cmd.set_defaults(func=partial(start_list_markets, pairs_only=False)) self._build_args(optionlist=ARGS_LIST_PAIRS, parser=list_markets_cmd) @@ -187,7 +188,8 @@ class Arguments: # Add list-pairs subcommand list_pairs_cmd = subparsers.add_parser( 'list-pairs', - help='Print pairs on exchange.' + help='Print pairs on exchange.', + parents=[_common_parser], ) list_pairs_cmd.set_defaults(func=partial(start_list_markets, pairs_only=True)) self._build_args(optionlist=ARGS_LIST_PAIRS, parser=list_pairs_cmd) diff --git a/tests/test_utils.py b/tests/test_utils.py index c598cfd76..8cd9c3aef 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -182,8 +182,8 @@ def test_list_markets(mocker, markets, capsys): # Test with --config config.json.example args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--print-list", ] start_list_markets(get_args(args), False) @@ -208,8 +208,8 @@ def test_list_markets(mocker, markets, capsys): patch_exchange(mocker, api_mock=api_mock, id="bittrex") # Test with --all: all markets args = [ - '--config', 'config.json.example', "list-markets", "--all", + '--config', 'config.json.example', "--print-list", ] start_list_markets(get_args(args), False) @@ -221,8 +221,8 @@ def test_list_markets(mocker, markets, capsys): # Test list-pairs subcommand: active pairs args = [ - '--config', 'config.json.example', "list-pairs", + '--config', 'config.json.example', "--print-list", ] start_list_markets(get_args(args), True) @@ -233,8 +233,8 @@ def test_list_markets(mocker, markets, capsys): # Test list-pairs subcommand with --all: all pairs args = [ - '--config', 'config.json.example', "list-pairs", "--all", + '--config', 'config.json.example', "--print-list", ] start_list_markets(get_args(args), True) @@ -246,8 +246,8 @@ def test_list_markets(mocker, markets, capsys): # active markets, base=ETH, LTC args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--base", "ETH", "LTC", "--print-list", ] @@ -259,8 +259,8 @@ def test_list_markets(mocker, markets, capsys): # active markets, base=LTC args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--base", "LTC", "--print-list", ] @@ -272,8 +272,8 @@ def test_list_markets(mocker, markets, capsys): # active markets, quote=USDT, USD args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--quote", "USDT", "USD", "--print-list", ] @@ -285,8 +285,8 @@ def test_list_markets(mocker, markets, capsys): # active markets, quote=USDT args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--quote", "USDT", "--print-list", ] @@ -298,8 +298,8 @@ def test_list_markets(mocker, markets, capsys): # active markets, base=LTC, quote=USDT args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--base", "LTC", "--quote", "USDT", "--print-list", ] @@ -311,8 +311,8 @@ def test_list_markets(mocker, markets, capsys): # active pairs, base=LTC, quote=USDT args = [ - '--config', 'config.json.example', "list-pairs", + '--config', 'config.json.example', "--base", "LTC", "--quote", "USDT", "--print-list", ] @@ -324,8 +324,8 @@ def test_list_markets(mocker, markets, capsys): # active markets, base=LTC, quote=USDT, NONEXISTENT args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--base", "LTC", "--quote", "USDT", "NONEXISTENT", "--print-list", ] @@ -337,8 +337,8 @@ def test_list_markets(mocker, markets, capsys): # active markets, base=LTC, quote=NONEXISTENT args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--base", "LTC", "--quote", "NONEXISTENT", "--print-list", ] @@ -350,8 +350,8 @@ def test_list_markets(mocker, markets, capsys): # Test tabular output args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', ] start_list_markets(get_args(args), False) captured = capsys.readouterr() @@ -360,8 +360,8 @@ def test_list_markets(mocker, markets, capsys): # Test tabular output, no markets found args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--base", "LTC", "--quote", "NONEXISTENT", ] start_list_markets(get_args(args), False) @@ -372,8 +372,8 @@ def test_list_markets(mocker, markets, capsys): # Test --print-json args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--print-json" ] start_list_markets(get_args(args), False) @@ -383,8 +383,8 @@ def test_list_markets(mocker, markets, capsys): # Test --print-csv args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--print-csv" ] start_list_markets(get_args(args), False) @@ -395,8 +395,8 @@ def test_list_markets(mocker, markets, capsys): # Test --one-column args = [ - '--config', 'config.json.example', "list-markets", + '--config', 'config.json.example', "--one-column" ] start_list_markets(get_args(args), False) From 13255b370c270a556f2e5883de0ac88abc801667 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Oct 2019 06:30:07 +0200 Subject: [PATCH 034/157] Allow non-config to parse config --- freqtrade/configuration/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 7bb49dc31..393fd78be 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -93,7 +93,7 @@ class Configuration: :return: Configuration dictionary """ # Load all configs - config: Dict[str, Any] = self.load_from_files(self.args["config"]) + config: Dict[str, Any] = self.load_from_files(self.args.get("config", [])) # Keep a copy of the original configuration file config['original_config'] = deepcopy(config) From 466a3b87fcda0001ea4325ba0aef2c0457d59679 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 28 Oct 2019 16:13:31 +0100 Subject: [PATCH 035/157] Enhance tests to cover precision_filter correctly --- tests/conftest.py | 60 +++++++++++++++++++++++++++++++++ tests/pairlist/test_pairlist.py | 33 +++++++++++------- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4feae6a60..a291a6676 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -572,6 +572,44 @@ def get_markets(): } +@pytest.fixture +def shitcoinmarkets(markets): + """ + Fixture with shitcoin markets - used to test filters in pairlists + """ + shitmarkets = deepcopy(markets) + shitmarkets.update({'HOT/BTC': { + 'id': 'HOTBTC', + 'symbol': 'HOT/BTC', + 'base': 'HOT', + 'quote': 'BTC', + 'active': True, + 'precision': { + 'base': 8, + 'quote': 8, + 'amount': 0, + 'price': 8 + }, + 'limits': { + 'amount': { + 'min': 1.0, + 'max': 90000000.0 + }, + 'price': { + 'min': None, + 'max': None + }, + 'cost': { + 'min': 0.001, + 'max': None + } + }, + 'info': {}, + }, + }) + return shitmarkets + + @pytest.fixture def markets_empty(): return MagicMock(return_value=[]) @@ -866,6 +904,28 @@ def tickers(): 'quoteVolume': 1215.14489611, 'info': {} }, + 'HOT/BTC': { + 'symbol': 'HOT/BTC', + 'timestamp': 1572273518661, + 'datetime': '2019-10-28T14:38:38.661Z', + 'high': 0.00000011, + 'low': 0.00000009, + 'bid': 0.0000001, + 'bidVolume': 1476027288.0, + 'ask': 0.00000011, + 'askVolume': 820153831.0, + 'vwap': 0.0000001, + 'open': 0.00000009, + 'close': 0.00000011, + 'last': 0.00000011, + 'previousClose': 0.00000009, + 'change': 0.00000002, + 'percentage': 22.222, + 'average': None, + 'baseVolume': 1442290324.0, + 'quoteVolume': 143.78311994, + 'info': {} + }, 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 929fc0ba0..6f050a77d 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -2,11 +2,12 @@ from unittest.mock import MagicMock, PropertyMock +import pytest + from freqtrade import OperationalException from freqtrade.constants import AVAILABLE_PAIRLISTS from freqtrade.resolvers import PairListResolver -from tests.conftest import get_patched_freqtradebot -import pytest +from tests.conftest import get_patched_freqtradebot, log_has_re # whitelist, blacklist @@ -67,20 +68,25 @@ def test_refresh_pairlists(mocker, markets, whitelist_conf): assert whitelist_conf['exchange']['pair_blacklist'] == freqtradebot.pairlists.blacklist -def test_refresh_pairlist_dynamic(mocker, markets, tickers, whitelist_conf): +def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf): whitelist_conf['pairlist'] = {'method': 'VolumePairList', - 'config': {'number_assets': 5} + 'config': {'number_assets': 5, + 'precision_filter': False} } + mocker.patch.multiple( 'freqtrade.exchange.Exchange', - markets=PropertyMock(return_value=markets), get_tickers=tickers, exchange_has=MagicMock(return_value=True) ) freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) - + # Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=shitcoinmarkets), + ) # argument: use the whitelist dynamically by exchange-volume - whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'] + whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC'] freqtradebot.pairlists.refresh_pairlist() assert whitelist == freqtradebot.pairlists.whitelist @@ -108,19 +114,20 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): @pytest.mark.parametrize("precision_filter,base_currency,key,whitelist_result", [ - (False, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), - (False, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + (False, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC']), + (False, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC']), (False, "USDT", "quoteVolume", ['ETH/USDT']), (False, "ETH", "quoteVolume", []), # this replaces tests that were removed from test_exchange (True, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC"]), (True, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC"]) ]) -def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers, base_currency, key, - whitelist_result, precision_filter) -> None: +def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, + precision_filter, base_currency, key, whitelist_result, + caplog) -> None: whitelist_conf['pairlist']['method'] = 'VolumePairList' mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=shitcoinmarkets)) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, p, r: round(r, 8)) @@ -128,6 +135,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers, freqtrade.config['stake_currency'] = base_currency whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key) assert sorted(whitelist) == sorted(whitelist_result) + if precision_filter: + assert log_has_re(r'^Removed .* from whitelist, because stop price .* would be <= stop limit.*', caplog) def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: From 4ff035537b01cf454a3cb34b60f232ad48972a2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 28 Oct 2019 16:21:00 +0100 Subject: [PATCH 036/157] Simplify precision_filter code --- config_full.json.example | 2 +- docs/configuration.md | 5 ++--- freqtrade/pairlist/VolumePairList.py | 21 +++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 5789e49ac..a5af0f7a6 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -55,7 +55,7 @@ "config": { "number_assets": 20, "sort_key": "quoteVolume", - "precision_filter": false + "precision_filter": true } }, "exchange": { diff --git a/docs/configuration.md b/docs/configuration.md index 1ad13c87a..03f15e07d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -423,8 +423,7 @@ section of the configuration. * `VolumePairList` * It selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. - * There is a possibility to filter low-value coins that would not allow setting a stop loss -(set `precision_filter` parameter to `true` for this). + * By default, low-value coins that would not allow setting a stop loss are filtered out. (set `precision_filter` parameter to `false` to disable this behaviour). * `VolumePairList` does not consider `pair_whitelist`, but builds this automatically based the pairlist configuration. * Pairs in `pair_blacklist` are not considered for VolumePairList, even if all other filters would match. @@ -440,7 +439,7 @@ Example: "config": { "number_assets": 20, "sort_key": "quoteVolume", - "precision_filter": false + "precision_filter": true } }, ``` diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 5f53cd17b..5e20f0fb1 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -26,7 +26,7 @@ class VolumePairList(IPairList): 'for "pairlist.config.number_assets"') self._number_pairs = self._whitelistconf['number_assets'] self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') - self._precision_filter = self._whitelistconf.get('precision_filter', False) + self._precision_filter = self._whitelistconf.get('precision_filter', True) if not self._freqtrade.exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -76,18 +76,19 @@ class VolumePairList(IPairList): valid_tickers = [t for t in sorted_tickers if t["symbol"] in valid_pairs] if self._freqtrade.strategy.stoploss is not None and self._precision_filter: + # Precalculate correct stoploss value + stoploss = 1 - abs(self._freqtrade.strategy.stoploss) - stop_prices = [self._freqtrade.get_target_bid(t["symbol"], t) - * (1 - abs(self._freqtrade.strategy.stoploss)) for t in valid_tickers] - rates = [sp * 0.99 for sp in stop_prices] - logger.debug("\n".join([f"{sp} : {r}" for sp, r in zip(stop_prices[:10], rates[:10])])) for i, t in enumerate(valid_tickers): - sp = self._freqtrade.exchange.symbol_price_prec(t["symbol"], stop_prices[i]) - r = self._freqtrade.exchange.symbol_price_prec(t["symbol"], rates[i]) - logger.debug(f"{t['symbol']} - {sp} : {r}") - if sp <= r: + stop_price = (self._freqtrade.get_target_bid(t["symbol"], t) * stoploss) + # Adjust stop-prices to precision + sp = self._freqtrade.exchange.symbol_price_prec(t["symbol"], stop_price) + stop_gap_price = self._freqtrade.exchange.symbol_price_prec(t["symbol"], + stop_price * 0.99) + logger.debug(f"{t['symbol']} - {sp} : {stop_gap_price}") + if sp <= stop_gap_price: logger.info(f"Removed {t['symbol']} from whitelist, " - f"because stop price {sp} would be <= stop limit {r}") + f"because stop price {sp} would be <= stop limit {stop_gap_price}") valid_tickers.remove(t) pairs = [s['symbol'] for s in valid_tickers] From d706571e6f9d29b6f843377bb7a2c0a62e778e6c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 28 Oct 2019 16:25:01 +0100 Subject: [PATCH 037/157] Extract precision_filter to seperate function --- freqtrade/pairlist/VolumePairList.py | 42 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 5e20f0fb1..44dbd0ecf 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -56,6 +56,27 @@ class VolumePairList(IPairList): self._whitelist = self._gen_pair_whitelist( self._config['stake_currency'], self._sort_key) + def _validate_precision_filter(self, ticker: dict, stoploss: float) -> bool: + """ + Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very + low value pairs. + :param ticker: ticker dict as returned from ccxt.load_markets() + :param stoploss: stoploss value as set in the configuration + (already cleaned to be guaranteed negative) + :return: True if the pair can stay, false if it should be removed + """ + stop_price = (self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss) + # Adjust stop-prices to precision + sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price) + stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], + stop_price * 0.99) + logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}") + if sp <= stop_gap_price: + logger.info(f"Removed {ticker['symbol']} from whitelist, " + f"because stop price {sp} would be <= stop limit {stop_gap_price}") + return False + return True + @cached(TTLCache(maxsize=1, ttl=1800)) def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: """ @@ -75,21 +96,16 @@ class VolumePairList(IPairList): valid_pairs = self._validate_whitelist([s['symbol'] for s in sorted_tickers]) valid_tickers = [t for t in sorted_tickers if t["symbol"] in valid_pairs] - if self._freqtrade.strategy.stoploss is not None and self._precision_filter: - # Precalculate correct stoploss value + stoploss = None + if self._freqtrade.strategy.stoploss is not None: + # Precalculate sanitized stoploss value to avoid recalculation for every pair stoploss = 1 - abs(self._freqtrade.strategy.stoploss) - for i, t in enumerate(valid_tickers): - stop_price = (self._freqtrade.get_target_bid(t["symbol"], t) * stoploss) - # Adjust stop-prices to precision - sp = self._freqtrade.exchange.symbol_price_prec(t["symbol"], stop_price) - stop_gap_price = self._freqtrade.exchange.symbol_price_prec(t["symbol"], - stop_price * 0.99) - logger.debug(f"{t['symbol']} - {sp} : {stop_gap_price}") - if sp <= stop_gap_price: - logger.info(f"Removed {t['symbol']} from whitelist, " - f"because stop price {sp} would be <= stop limit {stop_gap_price}") - valid_tickers.remove(t) + for t in valid_tickers: + # Filter out assets which would not allow setting a stoploss + if (stoploss and self._precision_filter + and not self._validate_precision_filter(t, stoploss)): + valid_tickers.remove(t) pairs = [s['symbol'] for s in valid_tickers] logger.info(f"Searching pairs: {pairs[:self._number_pairs]}") From d803d86f4d846ba8e67c2bde84b0de5efaea1ee6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 29 Oct 2019 06:53:26 +0100 Subject: [PATCH 038/157] Add low_price_percent_filter --- config_full.json.example | 3 ++- docs/configuration.md | 5 ++++- freqtrade/pairlist/VolumePairList.py | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index a5af0f7a6..5ae8021d5 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -55,7 +55,8 @@ "config": { "number_assets": 20, "sort_key": "quoteVolume", - "precision_filter": true + "precision_filter": true, + "low_price_percent_filter": null } }, "exchange": { diff --git a/docs/configuration.md b/docs/configuration.md index 03f15e07d..c7e0dac31 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -426,6 +426,8 @@ section of the configuration. * By default, low-value coins that would not allow setting a stop loss are filtered out. (set `precision_filter` parameter to `false` to disable this behaviour). * `VolumePairList` does not consider `pair_whitelist`, but builds this automatically based the pairlist configuration. * Pairs in `pair_blacklist` are not considered for VolumePairList, even if all other filters would match. + * `low_price_percent_filter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_percent_filter` ratio. + Calculation example: Min price precision is 8 decimals. If price is 0.00000011 - one step would be 0.00000012 - which is almost 10% higher than the previous value. Example: @@ -439,7 +441,8 @@ Example: "config": { "number_assets": 20, "sort_key": "quoteVolume", - "precision_filter": true + "precision_filter": true, + "low_price_percent_filter": 0.03 } }, ``` diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 44dbd0ecf..4a6768efa 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -27,6 +27,8 @@ class VolumePairList(IPairList): self._number_pairs = self._whitelistconf['number_assets'] self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') self._precision_filter = self._whitelistconf.get('precision_filter', True) + self._low_price_percent_filter = self._whitelistconf.get('low_price_percent_filter', None) + print(self._whitelistconf) if not self._freqtrade.exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -77,6 +79,23 @@ class VolumePairList(IPairList): return False return True + def _validate_precision_filter_lowprice(self, ticker) -> bool: + """ + Check if if one price-step is > than a certain barrier. + :param ticker: ticker dict as returned from ccxt.load_markets() + :param precision: Precision + :return: True if the pair can stay, false if it should be removed + """ + precision = self._freqtrade.exchange.markets[ticker['symbol']]['precision']['price'] + + compare = ticker['last'] + 1 / pow(10, precision) + changeperc = (compare - ticker['last']) / ticker['last'] + if changeperc > self._low_price_percent_filter: + logger.info(f"Removed {ticker['symbol']} from whitelist, " + f"because 1 unit is {changeperc * 100:.3f}%") + return False + return True + @cached(TTLCache(maxsize=1, ttl=1800)) def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: """ @@ -106,6 +125,10 @@ class VolumePairList(IPairList): if (stoploss and self._precision_filter and not self._validate_precision_filter(t, stoploss)): valid_tickers.remove(t) + continue + if self._low_price_percent_filter and not self._validate_precision_filter_lowprice(t,): + valid_tickers.remove(t) + continue pairs = [s['symbol'] for s in valid_tickers] logger.info(f"Searching pairs: {pairs[:self._number_pairs]}") From de2cc58b0cf0024256bd0c2c924d080838b5b86f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 29 Oct 2019 10:39:27 +0100 Subject: [PATCH 039/157] Final cleanups and added tests --- config_full.json.example | 2 +- docs/configuration.md | 4 ++- freqtrade/pairlist/VolumePairList.py | 15 +++++---- tests/conftest.py | 50 ++++++++++++++++++++++++++++ tests/pairlist/test_pairlist.py | 36 ++++++++++++-------- 5 files changed, 85 insertions(+), 22 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 5ae8021d5..5935de392 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -56,7 +56,7 @@ "number_assets": 20, "sort_key": "quoteVolume", "precision_filter": true, - "low_price_percent_filter": null + "low_price_percent_filter": 0 } }, "exchange": { diff --git a/docs/configuration.md b/docs/configuration.md index c7e0dac31..7ccb6840a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -427,7 +427,9 @@ section of the configuration. * `VolumePairList` does not consider `pair_whitelist`, but builds this automatically based the pairlist configuration. * Pairs in `pair_blacklist` are not considered for VolumePairList, even if all other filters would match. * `low_price_percent_filter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_percent_filter` ratio. + This option is disabled by default, and will only apply if set to <> 0. Calculation example: Min price precision is 8 decimals. If price is 0.00000011 - one step would be 0.00000012 - which is almost 10% higher than the previous value. + Example: @@ -442,7 +444,7 @@ Example: "number_assets": 20, "sort_key": "quoteVolume", "precision_filter": true, - "low_price_percent_filter": 0.03 + "low_price_percent_filter": 0.05 } }, ``` diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 4a6768efa..7c17c6d57 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -5,11 +5,14 @@ Provides lists as configured in config.json """ import logging +from copy import deepcopy from typing import List + from cachetools import TTLCache, cached -from freqtrade.pairlist.IPairList import IPairList from freqtrade import OperationalException +from freqtrade.pairlist.IPairList import IPairList + logger = logging.getLogger(__name__) SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] @@ -28,7 +31,6 @@ class VolumePairList(IPairList): self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') self._precision_filter = self._whitelistconf.get('precision_filter', True) self._low_price_percent_filter = self._whitelistconf.get('low_price_percent_filter', None) - print(self._whitelistconf) if not self._freqtrade.exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -64,10 +66,10 @@ class VolumePairList(IPairList): low value pairs. :param ticker: ticker dict as returned from ccxt.load_markets() :param stoploss: stoploss value as set in the configuration - (already cleaned to be guaranteed negative) + (already cleaned to be 1 - stoploss) :return: True if the pair can stay, false if it should be removed """ - stop_price = (self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss) + stop_price = self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss # Adjust stop-prices to precision sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price) stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], @@ -120,10 +122,11 @@ class VolumePairList(IPairList): # Precalculate sanitized stoploss value to avoid recalculation for every pair stoploss = 1 - abs(self._freqtrade.strategy.stoploss) - for t in valid_tickers: + # Copy list since we're modifying this list + for t in deepcopy(valid_tickers): # Filter out assets which would not allow setting a stoploss if (stoploss and self._precision_filter - and not self._validate_precision_filter(t, stoploss)): + and not self._validate_precision_filter(t, stoploss)): valid_tickers.remove(t) continue if self._low_price_percent_filter and not self._validate_precision_filter_lowprice(t,): diff --git a/tests/conftest.py b/tests/conftest.py index a291a6676..d551596f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -606,6 +606,34 @@ def shitcoinmarkets(markets): }, 'info': {}, }, + 'FUEL/BTC': { + 'id': 'FUELBTC', + 'symbol': 'FUEL/BTC', + 'base': 'FUEL', + 'quote': 'BTC', + 'active': True, + 'precision': { + 'base': 8, + 'quote': 8, + 'amount': 0, + 'price': 8 + }, + 'limits': { + 'amount': { + 'min': 1.0, + 'max': 90000000.0 + }, + 'price': { + 'min': 1e-08, + 'max': 1000.0 + }, + 'cost': { + 'min': 0.001, + 'max': None + } + }, + 'info': {}, + }, }) return shitmarkets @@ -926,6 +954,28 @@ def tickers(): 'quoteVolume': 143.78311994, 'info': {} }, + 'FUEL/BTC': { + 'symbol': 'FUEL/BTC', + 'timestamp': 1572340250771, + 'datetime': '2019-10-29T09:10:50.771Z', + 'high': 0.00000040, + 'low': 0.00000035, + 'bid': 0.00000036, + 'bidVolume': 8932318.0, + 'ask': 0.00000037, + 'askVolume': 10140774.0, + 'vwap': 0.00000037, + 'open': 0.00000039, + 'close': 0.00000037, + 'last': 0.00000037, + 'previousClose': 0.00000038, + 'change': -0.00000002, + 'percentage': -5.128, + 'average': None, + 'baseVolume': 168927742.0, + 'quoteVolume': 62.68220262, + 'info': {} + }, 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 6f050a77d..ac27d1c8e 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -26,7 +26,7 @@ def whitelist_conf(default_conf): 'BLK/BTC' ] default_conf['pairlist'] = {'method': 'StaticPairList', - 'config': {'number_assets': 3} + 'config': {'number_assets': 5} } return default_conf @@ -86,7 +86,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co markets=PropertyMock(return_value=shitcoinmarkets), ) # argument: use the whitelist dynamically by exchange-volume - whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC'] + whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC'] freqtradebot.pairlists.refresh_pairlist() assert whitelist == freqtradebot.pairlists.whitelist @@ -113,30 +113,38 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): assert set(whitelist) == set(pairslist) -@pytest.mark.parametrize("precision_filter,base_currency,key,whitelist_result", [ - (False, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC']), - (False, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC']), - (False, "USDT", "quoteVolume", ['ETH/USDT']), - (False, "ETH", "quoteVolume", []), # this replaces tests that were removed from test_exchange - (True, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC"]), - (True, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC"]) +@pytest.mark.parametrize("precision_filter,low_price_filter,base_currency,key,whitelist_result", [ + (False, 0, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + (False, 0, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']), + (False, 0, "USDT", "quoteVolume", ['ETH/USDT']), + (False, 0, "ETH", "quoteVolume", []), + (True, 0, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']), + (True, 0, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC", 'FUEL/BTC']), + (False, 0.03, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'FUEL/BTC']), + # Hot is removed by precision_filter, Fuel by low_price_filter. + (True, 0.02, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, - precision_filter, base_currency, key, whitelist_result, - caplog) -> None: + precision_filter, low_price_filter, base_currency, key, + whitelist_result, caplog) -> None: whitelist_conf['pairlist']['method'] = 'VolumePairList' + whitelist_conf['pairlist']['config']['precision_filter'] = precision_filter + whitelist_conf['pairlist']['config']['low_price_percent_filter'] = low_price_filter + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=shitcoinmarkets)) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, p, r: round(r, 8)) - freqtrade.pairlists._precision_filter = precision_filter freqtrade.config['stake_currency'] = base_currency whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key) assert sorted(whitelist) == sorted(whitelist_result) if precision_filter: - assert log_has_re(r'^Removed .* from whitelist, because stop price .* would be <= stop limit.*', caplog) + assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' + r'would be <= stop limit.*', caplog) + + if low_price_filter: + assert log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: From 44289e4c586324e1026ad0de03e1ef3c746128bd Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 15:55:35 +0100 Subject: [PATCH 040/157] Allow not using files from user_dir --- freqtrade/resolvers/iresolver.py | 10 +++++----- freqtrade/resolvers/pairlist_resolver.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 51c4f7dba..3bad42fd9 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -17,13 +17,13 @@ class IResolver: This class contains all the logic to load custom classes """ - def build_search_paths(self, config, current_path: Path, user_subdir: str, + def build_search_paths(self, config, current_path: Path, user_subdir: Optional[str] = None, extra_dir: Optional[str] = None) -> List[Path]: - abs_paths = [ - config['user_data_dir'].joinpath(user_subdir), - current_path, - ] + abs_paths: List[Path] = [current_path] + + if user_subdir: + abs_paths.insert(0, config['user_data_dir'].joinpath(user_subdir)) if extra_dir: # Add extra directory to the top of the search paths diff --git a/freqtrade/resolvers/pairlist_resolver.py b/freqtrade/resolvers/pairlist_resolver.py index c2782a219..0f23bb3fd 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -40,7 +40,7 @@ class PairListResolver(IResolver): current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve() abs_paths = self.build_search_paths(config, current_path=current_path, - user_subdir='pairlist', extra_dir=None) + user_subdir=None, extra_dir=None) pairlist = self._load_object(paths=abs_paths, object_type=IPairList, object_name=pairlist_name, kwargs=kwargs) From fd9c02603cfea41876dd562e224e15025c0637a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 15:59:52 +0100 Subject: [PATCH 041/157] Introduce chainable PairlistFilters --- freqtrade/pairlist/IPairList.py | 36 +++++++++-- freqtrade/pairlist/IPairListFilter.py | 18 ++++++ freqtrade/pairlist/LowPriceFilter.py | 48 ++++++++++++++ freqtrade/pairlist/PrecisionFilter.py | 51 +++++++++++++++ freqtrade/pairlist/StaticPairList.py | 2 +- freqtrade/pairlist/VolumePairList.py | 62 +------------------ .../resolvers/pairlistfilter_resolver.py | 53 ++++++++++++++++ 7 files changed, 203 insertions(+), 67 deletions(-) create mode 100644 freqtrade/pairlist/IPairListFilter.py create mode 100644 freqtrade/pairlist/LowPriceFilter.py create mode 100644 freqtrade/pairlist/PrecisionFilter.py create mode 100644 freqtrade/resolvers/pairlistfilter_resolver.py diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 5afb0c4c2..eb6af9d52 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -6,10 +6,11 @@ Provides lists as configured in config.json """ import logging from abc import ABC, abstractmethod -from typing import List +from typing import Dict, List from freqtrade.exchange import market_is_active - +from freqtrade.pairlist.IPairListFilter import IPairListFilter +from freqtrade.resolvers.pairlistfilter_resolver import PairListFilterResolver logger = logging.getLogger(__name__) @@ -21,6 +22,12 @@ class IPairList(ABC): self._config = config self._whitelist = self._config['exchange']['pair_whitelist'] self._blacklist = self._config['exchange'].get('pair_blacklist', []) + self._filters = self._config.get('pairlist', {}).get('filters', {}) + self._pairlistfilters: List[IPairListFilter] = [] + for pl_filter in self._filters.keys(): + self._pairlistfilters.append( + PairListFilterResolver(pl_filter, freqtrade, self._config).pairlistfilter + ) @property def name(self) -> str: @@ -60,7 +67,23 @@ class IPairList(ABC): -> Please overwrite in subclasses """ - def _validate_whitelist(self, whitelist: List[str]) -> List[str]: + def validate_whitelist(self, pairlist: List[str], + tickers: List[Dict] = []) -> List[str]: + """ + Validate pairlist against active markets and blacklist. + Run PairlistFilters if these are configured. + """ + pairlist = self._whitelist_for_active_markets(pairlist) + + if not tickers: + # Refresh tickers if they are not used by the parent Pairlist + tickers = self._freqtrade.exchange.get_tickers() + + for pl_filter in self._pairlistfilters: + pairlist = pl_filter.filter_pairlist(pairlist, tickers) + return pairlist + + def _whitelist_for_active_markets(self, whitelist: List[str]) -> List[str]: """ Check available markets and remove pair from whitelist if necessary :param whitelist: the sorted list of pairs the user might want to trade @@ -69,7 +92,7 @@ class IPairList(ABC): """ markets = self._freqtrade.exchange.markets - sanitized_whitelist = set() + sanitized_whitelist: List[str] = [] for pair in whitelist: # pair is not in the generated dynamic market, or in the blacklist ... ignore it if (pair in self.blacklist or pair not in markets @@ -83,7 +106,8 @@ class IPairList(ABC): if not market_is_active(market): logger.info(f"Ignoring {pair} from whitelist. Market is not active.") continue - sanitized_whitelist.add(pair) + if pair not in sanitized_whitelist: + sanitized_whitelist.append(pair) # We need to remove pairs that are unknown - return list(sanitized_whitelist) + return sanitized_whitelist diff --git a/freqtrade/pairlist/IPairListFilter.py b/freqtrade/pairlist/IPairListFilter.py new file mode 100644 index 000000000..4b43f0e9f --- /dev/null +++ b/freqtrade/pairlist/IPairListFilter.py @@ -0,0 +1,18 @@ +import logging +from abc import ABC, abstractmethod +from typing import Dict, List + +logger = logging.getLogger(__name__) + + +class IPairListFilter(ABC): + + def __init__(self, freqtrade, config: dict) -> None: + self._freqtrade = freqtrade + self._config = config + + @abstractmethod + def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + """ + Method doing the filtering + """ diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py new file mode 100644 index 000000000..499dd0c15 --- /dev/null +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -0,0 +1,48 @@ +import logging +from copy import deepcopy +from typing import Dict, List + +from freqtrade.pairlist.IPairListFilter import IPairListFilter + +logger = logging.getLogger(__name__) + + +class LowPriceFilter(IPairListFilter): + + def __init__(self, freqtrade, config: dict) -> None: + super().__init__(freqtrade, config) + + self._low_price_percent = config['pairlist']['filters']['LowPriceFilter'].get( + 'low_price_percent', 0) + + def _validate_precision_filter_lowprice(self, ticker) -> bool: + """ + Check if if one price-step is > than a certain barrier. + :param ticker: ticker dict as returned from ccxt.load_markets() + :param precision: Precision + :return: True if the pair can stay, false if it should be removed + """ + precision = self._freqtrade.exchange.markets[ticker['symbol']]['precision']['price'] + + compare = ticker['last'] + 1 / pow(10, precision) + changeperc = (compare - ticker['last']) / ticker['last'] + if changeperc > self._low_price_percent: + logger.info(f"Removed {ticker['symbol']} from whitelist, " + f"because 1 unit is {changeperc * 100:.3f}%") + return False + return True + + def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + """ + Method doing the filtering + """ + + # Copy list since we're modifying this list + for p in deepcopy(pairlist): + ticker = [t for t in tickers if t['symbol'] == p][0] + + # Filter out assets which would not allow setting a stoploss + if self._low_price_percent and not self._validate_precision_filter_lowprice(ticker): + pairlist.remove(p) + + return pairlist diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py new file mode 100644 index 000000000..c720b8e61 --- /dev/null +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -0,0 +1,51 @@ +import logging +from copy import deepcopy +from typing import Dict, List + +from freqtrade.pairlist.IPairListFilter import IPairListFilter + +logger = logging.getLogger(__name__) + + +class PrecisionFilter(IPairListFilter): + + def __init__(self, freqtrade, config: dict) -> None: + super().__init__(freqtrade, config) + + def _validate_precision_filter(self, ticker: dict, stoploss: float) -> bool: + """ + Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very + low value pairs. + :param ticker: ticker dict as returned from ccxt.load_markets() + :param stoploss: stoploss value as set in the configuration + (already cleaned to be 1 - stoploss) + :return: True if the pair can stay, false if it should be removed + """ + stop_price = self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss + # Adjust stop-prices to precision + sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price) + stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], + stop_price * 0.99) + logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}") + if sp <= stop_gap_price: + logger.info(f"Removed {ticker['symbol']} from whitelist, " + f"because stop price {sp} would be <= stop limit {stop_gap_price}") + return False + return True + + def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + """ + Method doing the filtering + """ + if self._freqtrade.strategy.stoploss is not None: + # Precalculate sanitized stoploss value to avoid recalculation for every pair + stoploss = 1 - abs(self._freqtrade.strategy.stoploss) + # Copy list since we're modifying this list + for p in deepcopy(pairlist): + ticker = [t for t in tickers if t['symbol'] == p][0] + # Filter out assets which would not allow setting a stoploss + if (stoploss and not self._validate_precision_filter(ticker, stoploss)): + pairlist.remove(p) + continue + + return pairlist diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 5896e814a..074652b25 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -27,4 +27,4 @@ class StaticPairList(IPairList): """ Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively """ - self._whitelist = self._validate_whitelist(self._config['exchange']['pair_whitelist']) + self._whitelist = self.validate_whitelist(self._config['exchange']['pair_whitelist']) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 7c17c6d57..911bb3bda 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -5,7 +5,6 @@ Provides lists as configured in config.json """ import logging -from copy import deepcopy from typing import List from cachetools import TTLCache, cached @@ -30,7 +29,6 @@ class VolumePairList(IPairList): self._number_pairs = self._whitelistconf['number_assets'] self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') self._precision_filter = self._whitelistconf.get('precision_filter', True) - self._low_price_percent_filter = self._whitelistconf.get('low_price_percent_filter', None) if not self._freqtrade.exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -60,44 +58,6 @@ class VolumePairList(IPairList): self._whitelist = self._gen_pair_whitelist( self._config['stake_currency'], self._sort_key) - def _validate_precision_filter(self, ticker: dict, stoploss: float) -> bool: - """ - Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very - low value pairs. - :param ticker: ticker dict as returned from ccxt.load_markets() - :param stoploss: stoploss value as set in the configuration - (already cleaned to be 1 - stoploss) - :return: True if the pair can stay, false if it should be removed - """ - stop_price = self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss - # Adjust stop-prices to precision - sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price) - stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], - stop_price * 0.99) - logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}") - if sp <= stop_gap_price: - logger.info(f"Removed {ticker['symbol']} from whitelist, " - f"because stop price {sp} would be <= stop limit {stop_gap_price}") - return False - return True - - def _validate_precision_filter_lowprice(self, ticker) -> bool: - """ - Check if if one price-step is > than a certain barrier. - :param ticker: ticker dict as returned from ccxt.load_markets() - :param precision: Precision - :return: True if the pair can stay, false if it should be removed - """ - precision = self._freqtrade.exchange.markets[ticker['symbol']]['precision']['price'] - - compare = ticker['last'] + 1 / pow(10, precision) - changeperc = (compare - ticker['last']) / ticker['last'] - if changeperc > self._low_price_percent_filter: - logger.info(f"Removed {ticker['symbol']} from whitelist, " - f"because 1 unit is {changeperc * 100:.3f}%") - return False - return True - @cached(TTLCache(maxsize=1, ttl=1800)) def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: """ @@ -114,26 +74,8 @@ class VolumePairList(IPairList): and v[key] is not None)] sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) # Validate whitelist to only have active market pairs - valid_pairs = self._validate_whitelist([s['symbol'] for s in sorted_tickers]) - valid_tickers = [t for t in sorted_tickers if t["symbol"] in valid_pairs] + pairs = self.validate_whitelist([s['symbol'] for s in sorted_tickers], tickers) - stoploss = None - if self._freqtrade.strategy.stoploss is not None: - # Precalculate sanitized stoploss value to avoid recalculation for every pair - stoploss = 1 - abs(self._freqtrade.strategy.stoploss) - - # Copy list since we're modifying this list - for t in deepcopy(valid_tickers): - # Filter out assets which would not allow setting a stoploss - if (stoploss and self._precision_filter - and not self._validate_precision_filter(t, stoploss)): - valid_tickers.remove(t) - continue - if self._low_price_percent_filter and not self._validate_precision_filter_lowprice(t,): - valid_tickers.remove(t) - continue - - pairs = [s['symbol'] for s in valid_tickers] - logger.info(f"Searching pairs: {pairs[:self._number_pairs]}") + logger.info(f"Searching {self._number_pairs} pairs: {pairs[:self._number_pairs]}") return pairs diff --git a/freqtrade/resolvers/pairlistfilter_resolver.py b/freqtrade/resolvers/pairlistfilter_resolver.py new file mode 100644 index 000000000..bf86d1c6c --- /dev/null +++ b/freqtrade/resolvers/pairlistfilter_resolver.py @@ -0,0 +1,53 @@ +# pragma pylint: disable=attribute-defined-outside-init + +""" +This module load custom pairlists +""" +import logging +from pathlib import Path + +from freqtrade import OperationalException +from freqtrade.pairlist.IPairListFilter import IPairListFilter +from freqtrade.resolvers import IResolver + +logger = logging.getLogger(__name__) + + +class PairListFilterResolver(IResolver): + """ + This class contains all the logic to load custom PairList class + """ + + __slots__ = ['pairlistfilter'] + + def __init__(self, pairlist_name: str, freqtrade, config: dict) -> None: + """ + Load the custom class from config parameter + :param config: configuration dictionary or None + """ + self.pairlistfilter = self._load_pairlist(pairlist_name, config, + kwargs={'freqtrade': freqtrade, + 'config': config}) + + def _load_pairlist( + self, pairlistfilter_name: str, config: dict, kwargs: dict) -> IPairListFilter: + """ + Search and loads the specified pairlist. + :param pairlistfilter_name: name of the module to import + :param config: configuration dictionary + :param extra_dir: additional directory to search for the given pairlist + :return: PairList instance or None + """ + current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve() + + abs_paths = self.build_search_paths(config, current_path=current_path, + user_subdir=None, extra_dir=None) + + pairlist = self._load_object(paths=abs_paths, object_type=IPairListFilter, + object_name=pairlistfilter_name, kwargs=kwargs) + if pairlist: + return pairlist + raise OperationalException( + f"Impossible to load PairlistFilter '{pairlistfilter_name}'. This class does not exist " + "or contains Python code errors." + ) From 640423c362195224aa62a7287e35f6bfee1a6508 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 16:00:16 +0100 Subject: [PATCH 042/157] Add config samples for chainable pairlist filters --- config_full.json.example | 6 +++-- tests/pairlist/test_pairlist.py | 40 +++++++++++++++++---------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 5935de392..366b075fa 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -55,8 +55,10 @@ "config": { "number_assets": 20, "sort_key": "quoteVolume", - "precision_filter": true, - "low_price_percent_filter": 0 + }, + "filters":{ + "PrecisionFilter": {}, + "LowPriceFilter": {"low_price_percent": 0.01} } }, "exchange": { diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index ac27d1c8e..23c48545e 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -26,7 +26,8 @@ def whitelist_conf(default_conf): 'BLK/BTC' ] default_conf['pairlist'] = {'method': 'StaticPairList', - 'config': {'number_assets': 5} + 'config': {'number_assets': 5}, + 'filters': {}, } return default_conf @@ -113,23 +114,24 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): assert set(whitelist) == set(pairslist) -@pytest.mark.parametrize("precision_filter,low_price_filter,base_currency,key,whitelist_result", [ - (False, 0, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), - (False, 0, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']), - (False, 0, "USDT", "quoteVolume", ['ETH/USDT']), - (False, 0, "ETH", "quoteVolume", []), - (True, 0, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']), - (True, 0, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC", 'FUEL/BTC']), - (False, 0.03, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'FUEL/BTC']), +@pytest.mark.parametrize("filters,base_currency,key,whitelist_result", [ + ({}, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + ({}, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']), + ({}, "USDT", "quoteVolume", ['ETH/USDT']), + ({}, "ETH", "quoteVolume", []), + ({"PrecisionFilter": {}}, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']), + ({"PrecisionFilter": {}}, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC", 'FUEL/BTC']), + ({"LowPriceFilter": {"low_price_percent": 0.03}}, "BTC", + "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'FUEL/BTC']), # Hot is removed by precision_filter, Fuel by low_price_filter. - (True, 0.02, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + ({"PrecisionFilter": {}, "LowPriceFilter": {"low_price_percent": 0.02}}, + "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, - precision_filter, low_price_filter, base_currency, key, - whitelist_result, caplog) -> None: + filters, base_currency, key, whitelist_result, + caplog) -> None: whitelist_conf['pairlist']['method'] = 'VolumePairList' - whitelist_conf['pairlist']['config']['precision_filter'] = precision_filter - whitelist_conf['pairlist']['config']['low_price_percent_filter'] = low_price_filter + whitelist_conf['pairlist']['filters'] = filters mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) @@ -139,11 +141,11 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t freqtrade.config['stake_currency'] = base_currency whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key) assert sorted(whitelist) == sorted(whitelist_result) - if precision_filter: + if 'PrecisionFilter' in filters: assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' r'would be <= stop limit.*', caplog) - if low_price_filter: + if 'LowPriceFilter' in filters: assert log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) @@ -179,15 +181,15 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "is not compatible with exchange"), # BLK/BTC in blacklist (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active") # BTT/BTC is inactive ]) -def test_validate_whitelist(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, - log_message): +def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, + log_message): whitelist_conf['pairlist']['method'] = pairlist mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) caplog.clear() - new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist) + new_whitelist = freqtrade.pairlists._whitelist_for_active_markets(whitelist) assert set(new_whitelist) == set(['ETH/BTC', 'TKN/BTC']) assert log_message in caplog.text From d89a7d523502ed680e50387316cccca28ae5004d Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 16:30:47 +0100 Subject: [PATCH 043/157] Document new method to configure filters --- docs/configuration.md | 126 ++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 47 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 7ccb6840a..39fb4b8ac 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -377,6 +377,82 @@ The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "USDT" ``` +## Pairlists + +Pairlists define the list of pairs that the bot should trade. +There are [`StaticPairList`](#static-pair-list) and dynamic Whitelists available. + +In addition to pairlists, [pairlist filters](#available-pairlist-filters) can be configured, which remove certain assets. +These Filters work with all Pairlist providers and are applied in the sequence they occur. + +### Available Pairlists + +* [`StaticPairList`](#static-pair-list) (default, if not configured differently) +* [`VolumePairList`](#volume-pair-list) + +#### Static Pair List + +By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. Inactive markets and blacklisted pairs are removed from the pair_whitelist. + +It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`. + +#### Volume Pair List + +`VolumePairList` selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume` and defaults to `quoteVolume`. + +`VolumePairList` does not consider `pair_whitelist`, but selects the top assets from all available markets (with matching stake-currency) on the exchange. +Pairs in `pair_blacklist` are not considered for `VolumePairList`, even if all other filters would match. + +```json +"pairlist": { + "method": "VolumePairList", + "config": { + "number_assets": 20, + "sort_key": "quoteVolume", + }, +``` + +### Available Pairlist Filters + +* [`PrecisionFilter`](#precision-filter) +* [`LowPriceFilter`](#low-price-pair-filter) + +#### Precision Filter + +Filters low-value coins which would not allow setting a stoploss. + +#### Low Price Pair Filter + +The `LowPriceFilter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_percent` ratio. +This option is disabled by default, and will only apply if set to <> 0. + +Calculation example: +Min price precision is 8 decimals. If price is 0.00000011 - one step would be 0.00000012 - which is almost 10% higher than the previous value. + +These pairs are dangerous since it may be impossible to place the desired stoploss - and often result in high losses. + +### Full Pairlist example + +The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting by `quoteVolume` and applies both [`PrecisionFilter`](#precision-filter) and [`LowPriceFilter`](#low-price-pair-filter), filtering all assets where 1 priceunit is > 1%. + +```json +"exchange": { + "pair_whitelist": [], + "pair_blacklist": ["BNB/BTC"] +}, +"pairlist": { + "method": "VolumePairList", + "config": { + "number_assets": 20, + "sort_key": "quoteVolume", + }, + "filters":{ + "PrecisionFilter": {}, + "LowPriceFilter": {"low_price_percent": 0.01} + } + }, +``` + ## Switch to Dry-run mode We recommend starting the bot in the Dry-run mode to see how your bot will @@ -406,49 +482,6 @@ creating trades on the exchange. Once you will be happy with your bot performance running in the Dry-run mode, you can switch it to production mode. -### Dynamic Pairlists - -Dynamic pairlists select pairs for you based on the logic configured. -The bot runs against all pairs (with that stake) on the exchange, and a number of assets -(`number_assets`) is selected based on the selected criteria. - -By default, the `StaticPairList` method is used. -The Pairlist method is configured as `pair_whitelist` parameter under the `exchange` -section of the configuration. - -**Available Pairlist methods:** - -* `StaticPairList` - * It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`. -* `VolumePairList` - * It selects `number_assets` top pairs based on `sort_key`, which can be one of -`askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. - * By default, low-value coins that would not allow setting a stop loss are filtered out. (set `precision_filter` parameter to `false` to disable this behaviour). - * `VolumePairList` does not consider `pair_whitelist`, but builds this automatically based the pairlist configuration. - * Pairs in `pair_blacklist` are not considered for VolumePairList, even if all other filters would match. - * `low_price_percent_filter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_percent_filter` ratio. - This option is disabled by default, and will only apply if set to <> 0. - Calculation example: Min price precision is 8 decimals. If price is 0.00000011 - one step would be 0.00000012 - which is almost 10% higher than the previous value. - - -Example: - -```json -"exchange": { - "pair_whitelist": [], - "pair_blacklist": ["BNB/BTC"] -}, -"pairlist": { - "method": "VolumePairList", - "config": { - "number_assets": 20, - "sort_key": "quoteVolume", - "precision_filter": true, - "low_price_percent_filter": 0.05 - } - }, -``` - ## Switch to production mode In production mode, the bot will engage your money. Be careful, since a wrong @@ -479,7 +512,7 @@ you run it in production mode. !!! Note If you have an exchange API key yet, [see our tutorial](/pre-requisite). -### Using proxy with FreqTrade +## Using proxy with FreqTrade To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration. @@ -499,14 +532,13 @@ export HTTPS_PROXY="http://addr:port" freqtrade ``` - -### Embedding Strategies +## Embedding Strategies FreqTrade provides you with with an easy way to embed the strategy into your configuration file. This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field, in your chosen config file. -#### Encoding a string as BASE64 +### Encoding a string as BASE64 This is a quick example, how to generate the BASE64 string in python From 14758dbe107d4a460444719c78b7f9fd5477b5b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 16:32:22 +0100 Subject: [PATCH 044/157] Some small cleanups --- freqtrade/configuration/deprecated_settings.py | 7 +++++++ freqtrade/pairlist/LowPriceFilter.py | 4 ++-- tests/test_configuration.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index f00b23894..8471028aa 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -57,3 +57,10 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: 'experimental', 'sell_profit_only') process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal', 'experimental', 'ignore_roi_if_buy_signal') + + if config.get('pairlist', {}).get('config', {}).get('precision_filter'): + logger.warning( + "DEPRECATED: " + f"Using precision_filter setting is deprecated and has been replaced by" + "PrecisionFilter. Please refer to the docs on configuration details") + config['pairlist'].update({'filters': {'PrecisionFilter': {}}}) diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py index 499dd0c15..778c9b4e0 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -15,7 +15,7 @@ class LowPriceFilter(IPairListFilter): self._low_price_percent = config['pairlist']['filters']['LowPriceFilter'].get( 'low_price_percent', 0) - def _validate_precision_filter_lowprice(self, ticker) -> bool: + def _validate_ticker_lowprice(self, ticker) -> bool: """ Check if if one price-step is > than a certain barrier. :param ticker: ticker dict as returned from ccxt.load_markets() @@ -42,7 +42,7 @@ class LowPriceFilter(IPairListFilter): ticker = [t for t in tickers if t['symbol'] == p][0] # Filter out assets which would not allow setting a stoploss - if self._low_price_percent and not self._validate_precision_filter_lowprice(ticker): + if self._low_price_percent and not self._validate_ticker_lowprice(ticker): pairlist.remove(p) return pairlist diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 545dd5df4..258088925 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -963,6 +963,16 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca assert default_conf[setting[0]][setting[1]] == setting[5] +def test_process_deprecated_setting_precision_filter(mocker, default_conf, caplog): + patched_configuration_load_config_file(mocker, default_conf) + default_conf.update({'pairlist': { + 'config': {'precision_filter': True} + }}) + + process_temporary_deprecated_settings(default_conf) + assert log_has_re(r'DEPRECATED.*precision_filter.*', caplog) + + def test_check_conflicting_settings(mocker, default_conf, caplog): patched_configuration_load_config_file(mocker, default_conf) From ad98d61939f9808b0adffb0b06099fdef241eee2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 30 Oct 2019 16:39:45 +0100 Subject: [PATCH 045/157] Update developer docs --- docs/developer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer.md b/docs/developer.md index 391493b09..346578c2e 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -127,7 +127,7 @@ This is called with each iteration of the bot - so consider implementing caching Assign the resulting whiteslist to `self._whitelist` and `self._blacklist` respectively. These will then be used to run the bot in this iteration. Pairs with open trades will be added to the whitelist to have the sell-methods run correctly. -Please also run `self._validate_whitelist(pairs)` and to check and remove pairs with inactive markets. This function is available in the Parent class (`StaticPairList`) and should ideally not be overwritten. +Please also run `self.validate_whitelist(pairs, tickers)` (tickers is optional, but should be passed when you're using tickers anyway) and to check and remove pairs with inactive markets. This function is available in the Parent class (`StaticPairList`) and should ideally not be overwritten. ##### sample @@ -136,7 +136,7 @@ Please also run `self._validate_whitelist(pairs)` and to check and remove pairs # Generate dynamic whitelist pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) # Validate whitelist to only have active market pairs - self._whitelist = self._validate_whitelist(pairs)[:self._number_pairs] + self._whitelist = self.validate_whitelist(pairs)[:self._number_pairs] ``` #### _gen_pair_whitelist From da57396d071131102c366d9821e13566cf594998 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Nov 2019 06:55:07 +0100 Subject: [PATCH 046/157] Fix UTC handling of timestamp() conversation in fetch_my_trades --- freqtrade/data/btanalysis.py | 6 +++--- freqtrade/data/history.py | 7 +++---- freqtrade/exchange/exchange.py | 19 ++++++++++++++++++- tests/exchange/test_exchange.py | 17 +++++++++++++++-- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 388deb4b3..2f7a234ce 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -7,7 +7,7 @@ from typing import Dict import numpy as np import pandas as pd -import pytz +from datetime import timezone from freqtrade import persistence from freqtrade.misc import json_load @@ -106,8 +106,8 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: "stop_loss", "initial_stop_loss", "strategy", "ticker_interval"] trades = pd.DataFrame([(t.pair, - t.open_date.replace(tzinfo=pytz.UTC), - t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None, + t.open_date.replace(tzinfo=timezone.utc), + t.close_date.replace(tzinfo=timezone.utc) if t.close_date else None, t.calc_profit(), t.calc_profit_percent(), t.open_rate, t.close_rate, t.amount, (round((t.close_date.timestamp() - t.open_date.timestamp()) / 60, 2) diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index b1e4313ca..3dd40d2b4 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -9,12 +9,11 @@ Includes: import logging import operator from copy import deepcopy -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List, Optional, Tuple import arrow -import pytz from pandas import DataFrame from freqtrade import OperationalException, misc @@ -56,10 +55,10 @@ def trim_dataframe(df: DataFrame, timerange: TimeRange) -> DataFrame: Trim dataframe based on given timerange """ if timerange.starttype == 'date': - start = datetime.fromtimestamp(timerange.startts, tz=pytz.utc) + start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) df = df.loc[df['date'] >= start, :] if timerange.stoptype == 'date': - stop = datetime.fromtimestamp(timerange.stopts, tz=pytz.utc) + stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) df = df.loc[df['date'] <= stop, :] return df diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 430a2ff54..a198e8cdb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -875,6 +875,22 @@ class Exchange: @retrier def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: + """ + Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. + The "since" argument passed in is coming from the database and is in UTC, + as timezone-native datetime object. + From the python documentation: + > Naive datetime instances are assumed to represent local time + Therefore, calling "since.timestamp()" will get the UTC timestamp, after applying the + transformation from local timezone to UTC. + This works for timezones UTC+ since then the result will contain trades from a few hours + instead of from the last 5 seconds, however fails for UTC- timezones, + since we're then asking for trades with a "since" argument in the future. + + :param order_id order_id: Order-id as given when creating the order + :param pair: Pair the order is for + :param since: datetime object of the order creation time. Assumes object is in UTC. + """ if self._config['dry_run']: return [] if not self.exchange_has('fetchMyTrades'): @@ -882,7 +898,8 @@ class Exchange: try: # Allow 5s offset to catch slight time offsets (discovered in #1185) # since needs to be int in milliseconds - my_trades = self._api.fetch_my_trades(pair, int((since.timestamp() - 5) * 1000)) + my_trades = self._api.fetch_my_trades( + pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000)) matched_trades = [trade for trade in my_trades if trade['order'] == order_id] return matched_trades diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 8a4121d80..1bb643e24 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1586,8 +1586,20 @@ def test_name(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_trades_for_order(default_conf, mocker, exchange_name): + """ + Crucial part in this test is the "since" calculation. + The "since" argument passed in is coming from the database and is in UTC, + as timezone-native datetime object. + From the python documentation: + > Naive datetime instances are assumed to represent local time + Therefore, calling "since.timestamp()" will get the UTC timestamp, after applying the + transformation from local timezone to UTC. + This works for timezones UTC+ since then the result will contain trades from a few hours + instead of from the last 5 seconds, however fails for UTC- timezones, + since we're then asking for trades with a "since" argument in the future. + """ order_id = 'ABCD-ABCD' - since = datetime(2018, 5, 5, tzinfo=timezone.utc) + since = datetime(2018, 5, 5, 0, 0, 0) default_conf["dry_run"] = False mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) api_mock = MagicMock() @@ -1623,7 +1635,8 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name): assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC' # Same test twice, hardcoded number and doing the same calculation assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000 - assert api_mock.fetch_my_trades.call_args[0][1] == int(since.timestamp() - 5) * 1000 + assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace( + tzinfo=timezone.utc).timestamp() - 5) * 1000 ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, 'get_trades_for_order', 'fetch_my_trades', From dd47bd04cd905c8822f5c27a285db8d37d72847f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Nov 2019 01:32:08 -0500 Subject: [PATCH 047/157] Move description to correct place --- tests/exchange/test_exchange.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 1bb643e24..925a53c95 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1586,18 +1586,7 @@ def test_name(default_conf, mocker, exchange_name): @pytest.mark.parametrize("exchange_name", EXCHANGES) def test_get_trades_for_order(default_conf, mocker, exchange_name): - """ - Crucial part in this test is the "since" calculation. - The "since" argument passed in is coming from the database and is in UTC, - as timezone-native datetime object. - From the python documentation: - > Naive datetime instances are assumed to represent local time - Therefore, calling "since.timestamp()" will get the UTC timestamp, after applying the - transformation from local timezone to UTC. - This works for timezones UTC+ since then the result will contain trades from a few hours - instead of from the last 5 seconds, however fails for UTC- timezones, - since we're then asking for trades with a "since" argument in the future. - """ + order_id = 'ABCD-ABCD' since = datetime(2018, 5, 5, 0, 0, 0) default_conf["dry_run"] = False From b0150d548a741153791fd1baf9e40394f75a38a1 Mon Sep 17 00:00:00 2001 From: Gautier Pialat Date: Fri, 8 Nov 2019 09:37:54 +0100 Subject: [PATCH 048/157] remove not use statement --- docs/installation.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 9180beb40..6edb28481 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -34,7 +34,6 @@ Freqtrade provides a Linux/MacOS script to install all dependencies and help you ```bash git clone git@github.com:freqtrade/freqtrade.git cd freqtrade -git checkout develop ./setup.sh --install ``` From 076ef0407b2057b6c8ad42c63e2ae9760d5ceb73 Mon Sep 17 00:00:00 2001 From: Gautier Pialat Date: Fri, 8 Nov 2019 09:39:06 +0100 Subject: [PATCH 049/157] git branch note explanation --- docs/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installation.md b/docs/installation.md index 6edb28481..d373c95d0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -37,6 +37,7 @@ cd freqtrade ./setup.sh --install ``` + When cloning the repository the default working branch is name `develop`. This branch contains the last features (can be considered as relatively stable thanks to automated tests). The `master` branch contains the code of the last release (done once per month with a one week old snapshot of the `develop` branch to prevent packaging bugs so potentially more stable). !!! Note Windows installation is explained [here](#windows). From bc5c91f6815b20ae64f6b8e8a5e29abac1ab51f2 Mon Sep 17 00:00:00 2001 From: Gautier Pialat Date: Fri, 8 Nov 2019 10:29:00 +0100 Subject: [PATCH 050/157] add missing note block --- docs/installation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index d373c95d0..ce35572c4 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -37,7 +37,9 @@ cd freqtrade ./setup.sh --install ``` +!!! Note "Version considerations" When cloning the repository the default working branch is name `develop`. This branch contains the last features (can be considered as relatively stable thanks to automated tests). The `master` branch contains the code of the last release (done once per month with a one week old snapshot of the `develop` branch to prevent packaging bugs so potentially more stable). + !!! Note Windows installation is explained [here](#windows). From 54b63e89f8a687ee9bd488fd010a57dd243ce355 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 8 Nov 2019 17:32:18 +0300 Subject: [PATCH 051/157] Wordings on top of #2495 --- docs/installation.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index ce35572c4..61ce6c7b2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -29,16 +29,18 @@ You will need to create API Keys (Usually you get `key` and `secret`) from the E Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot. !!! Note - Python3.6 or higher and the corresponding pip are assumed to be available. The install-script will warn and stop if that's not the case. + Python3.6 or higher and the corresponding `pip` are assumed to be available. The install-script will warn and stop if that's not the case. ```bash git clone git@github.com:freqtrade/freqtrade.git cd freqtrade +git checkout master # Optional, see (1) ./setup.sh --install ``` +(1) This command switches the cloned repository to the use of the `master` branch. This command is not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout master`/`git checkout develop` commands. !!! Note "Version considerations" - When cloning the repository the default working branch is name `develop`. This branch contains the last features (can be considered as relatively stable thanks to automated tests). The `master` branch contains the code of the last release (done once per month with a one week old snapshot of the `develop` branch to prevent packaging bugs so potentially more stable). + When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `master` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). !!! Note Windows installation is explained [here](#windows). From 1f042f5e32a92cf93ee721ecece6a5830408d35a Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Fri, 8 Nov 2019 19:38:32 +0300 Subject: [PATCH 052/157] Quick start and easy installation sections reworked --- docs/installation.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 61ce6c7b2..1330e0994 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -26,10 +26,20 @@ You will need to create API Keys (Usually you get `key` and `secret`) from the E ## Quick start -Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot. +Freqtrade provides the Linux/MacOS Easy Installation script to install all dependencies and help you configure the bot. !!! Note - Python3.6 or higher and the corresponding `pip` are assumed to be available. The install-script will warn and stop if that's not the case. + Windows installation is explained [here](#windows). + +The easiest way to install and run Freqtrade is to clone the bot GitHub repository and then run the Easy Installation script, if it's available for your platform. + +!!! Note "Version considerations" + When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `master` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). + +!!! Note + Python3.6 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository. + +This can be achieved with the following commands: ```bash git clone git@github.com:freqtrade/freqtrade.git @@ -37,17 +47,11 @@ cd freqtrade git checkout master # Optional, see (1) ./setup.sh --install ``` -(1) This command switches the cloned repository to the use of the `master` branch. This command is not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout master`/`git checkout develop` commands. +(1) This command switches the cloned repository to the use of the `master` branch. It's not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout master`/`git checkout develop` commands. -!!! Note "Version considerations" - When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `master` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). +## Easy Installation Script (Linux/MacOS) -!!! Note - Windows installation is explained [here](#windows). - -## Easy Installation - Linux Script - -If you are on Debian, Ubuntu or MacOS freqtrade provides a script to Install, Update, Configure, and Reset your bot. +If you are on Debian, Ubuntu or MacOS Freqtrade provides the script to install, update, configure and reset the codebase of your bot. ```bash $ ./setup.sh @@ -60,25 +64,25 @@ usage: ** --install ** -This script will install everything you need to run the bot: +With this option, the script will install everything you need to run the bot: * Mandatory software as: `ta-lib` * Setup your virtualenv * Configure your `config.json` file -This script is a combination of `install script` `--reset`, `--config` +This option is a combination of installation tasks, `--reset` and `--config`. ** --update ** -Update parameter will pull the last version of your current branch and update your virtualenv. +This option will pull the last version of your current branch and update your virtualenv. Run the script with this option periodically to update your bot. ** --reset ** -Reset parameter will hard reset your branch (only if you are on `master` or `develop`) and recreate your virtualenv. +This option will hard reset your branch (only if you are on either `master` or `develop`) and recreate your virtualenv. ** --config ** -Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`. +Use this option to configure the `config.json` configuration file. The script will interactively ask you questions to setup your bot and create your `config.json`. ------ From e632720c02bf982f2a4627d2ce8ce002b460ab97 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 06:55:16 +0100 Subject: [PATCH 053/157] Allow chaining of pairlists --- freqtrade/freqtradebot.py | 7 +- freqtrade/pairlist/IPairList.py | 65 ++++-------------- freqtrade/pairlist/IPairListFilter.py | 18 ----- freqtrade/pairlist/LowPriceFilter.py | 29 +++++--- freqtrade/pairlist/PrecisionFilter.py | 13 ++-- freqtrade/pairlist/StaticPairList.py | 15 ++-- freqtrade/pairlist/VolumePairList.py | 32 +++++---- freqtrade/pairlist/pairlistmanager.py | 68 +++++++++++++++++++ freqtrade/resolvers/pairlist_resolver.py | 8 ++- .../resolvers/pairlistfilter_resolver.py | 53 --------------- 10 files changed, 143 insertions(+), 165 deletions(-) delete mode 100644 freqtrade/pairlist/IPairListFilter.py create mode 100644 freqtrade/pairlist/pairlistmanager.py delete mode 100644 freqtrade/resolvers/pairlistfilter_resolver.py diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a8fc6bc7e..9871ffb89 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,9 +20,9 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date from freqtrade.persistence import Trade -from freqtrade.resolvers import (ExchangeResolver, PairListResolver, - StrategyResolver) +from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.rpc import RPCManager, RPCMessageType +from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.state import State from freqtrade.strategy.interface import IStrategy, SellType from freqtrade.wallets import Wallets @@ -70,8 +70,7 @@ class FreqtradeBot: # Attach Wallets to Strategy baseclass IStrategy.wallets = self.wallets - pairlistname = self.config.get('pairlist', {}).get('method', 'StaticPairList') - self.pairlists = PairListResolver(pairlistname, self, self.config).pairlist + self.pairlists = PairListManager(self.exchange, self.config) # Initializing Edge only if enabled self.edge = Edge(self.config, self.exchange, self.strategy) if \ diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index eb6af9d52..845c5d01f 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -9,25 +9,16 @@ from abc import ABC, abstractmethod from typing import Dict, List from freqtrade.exchange import market_is_active -from freqtrade.pairlist.IPairListFilter import IPairListFilter -from freqtrade.resolvers.pairlistfilter_resolver import PairListFilterResolver logger = logging.getLogger(__name__) class IPairList(ABC): - def __init__(self, freqtrade, config: dict) -> None: - self._freqtrade = freqtrade + def __init__(self, exchange, config, pairlistconfig: dict) -> None: + self._exchange = exchange self._config = config - self._whitelist = self._config['exchange']['pair_whitelist'] - self._blacklist = self._config['exchange'].get('pair_blacklist', []) - self._filters = self._config.get('pairlist', {}).get('filters', {}) - self._pairlistfilters: List[IPairListFilter] = [] - for pl_filter in self._filters.keys(): - self._pairlistfilters.append( - PairListFilterResolver(pl_filter, freqtrade, self._config).pairlistfilter - ) + self._pairlistconfig = pairlistconfig @property def name(self) -> str: @@ -37,22 +28,6 @@ class IPairList(ABC): """ return self.__class__.__name__ - @property - def whitelist(self) -> List[str]: - """ - Has the current whitelist - -> no need to overwrite in subclasses - """ - return self._whitelist - - @property - def blacklist(self) -> List[str]: - """ - Has the current blacklist - -> no need to overwrite in subclasses - """ - return self._blacklist - @abstractmethod def short_desc(self) -> str: """ @@ -61,28 +36,16 @@ class IPairList(ABC): """ @abstractmethod - def refresh_pairlist(self) -> None: + def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: """ - Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary -> Please overwrite in subclasses + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new whitelist """ - def validate_whitelist(self, pairlist: List[str], - tickers: List[Dict] = []) -> List[str]: - """ - Validate pairlist against active markets and blacklist. - Run PairlistFilters if these are configured. - """ - pairlist = self._whitelist_for_active_markets(pairlist) - - if not tickers: - # Refresh tickers if they are not used by the parent Pairlist - tickers = self._freqtrade.exchange.get_tickers() - - for pl_filter in self._pairlistfilters: - pairlist = pl_filter.filter_pairlist(pairlist, tickers) - return pairlist - def _whitelist_for_active_markets(self, whitelist: List[str]) -> List[str]: """ Check available markets and remove pair from whitelist if necessary @@ -90,16 +53,14 @@ class IPairList(ABC): :return: the list of pairs the user wants to trade without those unavailable or black_listed """ - markets = self._freqtrade.exchange.markets + markets = self._exchange.markets sanitized_whitelist: List[str] = [] for pair in whitelist: - # pair is not in the generated dynamic market, or in the blacklist ... ignore it - if (pair in self.blacklist or pair not in markets - or not pair.endswith(self._config['stake_currency'])): + # pair is not in the generated dynamic market or has the wrong stake currency + if (pair not in markets or not pair.endswith(self._config['stake_currency'])): logger.warning(f"Pair {pair} is not compatible with exchange " - f"{self._freqtrade.exchange.name} or contained in " - f"your blacklist. Removing it from whitelist..") + f"{self._exchange.name}. Removing it from whitelist..") continue # Check if market is active market = markets[pair] diff --git a/freqtrade/pairlist/IPairListFilter.py b/freqtrade/pairlist/IPairListFilter.py deleted file mode 100644 index 4b43f0e9f..000000000 --- a/freqtrade/pairlist/IPairListFilter.py +++ /dev/null @@ -1,18 +0,0 @@ -import logging -from abc import ABC, abstractmethod -from typing import Dict, List - -logger = logging.getLogger(__name__) - - -class IPairListFilter(ABC): - - def __init__(self, freqtrade, config: dict) -> None: - self._freqtrade = freqtrade - self._config = config - - @abstractmethod - def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: - """ - Method doing the filtering - """ diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py index 778c9b4e0..2f4a3be75 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -2,18 +2,23 @@ import logging from copy import deepcopy from typing import Dict, List -from freqtrade.pairlist.IPairListFilter import IPairListFilter +from freqtrade.pairlist.IPairList import IPairList logger = logging.getLogger(__name__) -class LowPriceFilter(IPairListFilter): +class LowPriceFilter(IPairList): - def __init__(self, freqtrade, config: dict) -> None: - super().__init__(freqtrade, config) + def __init__(self, exchange, config, pairlistconfig: dict) -> None: + super().__init__(exchange, config, pairlistconfig) - self._low_price_percent = config['pairlist']['filters']['LowPriceFilter'].get( - 'low_price_percent', 0) + self._low_price_percent = pairlistconfig.get('low_price_percent', 0) + + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return f"{self.name} - Filtering pairs priced below {self._low_price_percent * 100}%." def _validate_ticker_lowprice(self, ticker) -> bool: """ @@ -22,7 +27,7 @@ class LowPriceFilter(IPairListFilter): :param precision: Precision :return: True if the pair can stay, false if it should be removed """ - precision = self._freqtrade.exchange.markets[ticker['symbol']]['precision']['price'] + precision = self._exchange.markets[ticker['symbol']]['precision']['price'] compare = ticker['last'] + 1 / pow(10, precision) changeperc = (compare - ticker['last']) / ticker['last'] @@ -33,10 +38,14 @@ class LowPriceFilter(IPairListFilter): return True def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: - """ - Method doing the filtering - """ + """ + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new whitelist + """ # Copy list since we're modifying this list for p in deepcopy(pairlist): ticker = [t for t in tickers if t['symbol'] == p][0] diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index c720b8e61..0a590bec6 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -2,15 +2,18 @@ import logging from copy import deepcopy from typing import Dict, List -from freqtrade.pairlist.IPairListFilter import IPairListFilter +from freqtrade.pairlist.IPairList import IPairList logger = logging.getLogger(__name__) -class PrecisionFilter(IPairListFilter): +class PrecisionFilter(IPairList): - def __init__(self, freqtrade, config: dict) -> None: - super().__init__(freqtrade, config) + def short_desc(self) -> str: + """ + Short whitelist method description - used for startup-messages + """ + return f"{self.name} - Filtering untradable pairs." def _validate_precision_filter(self, ticker: dict, stoploss: float) -> bool: """ @@ -35,7 +38,7 @@ class PrecisionFilter(IPairListFilter): def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: """ - Method doing the filtering + Filters and sorts pairlists and assigns and returns them again. """ if self._freqtrade.strategy.stoploss is not None: # Precalculate sanitized stoploss value to avoid recalculation for every pair diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 074652b25..c10dde5a6 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -5,6 +5,7 @@ Provides lists as configured in config.json """ import logging +from typing import List, Dict from freqtrade.pairlist.IPairList import IPairList @@ -13,8 +14,8 @@ logger = logging.getLogger(__name__) class StaticPairList(IPairList): - def __init__(self, freqtrade, config: dict) -> None: - super().__init__(freqtrade, config) + def __init__(self, exchange, config, pairlistconfig: dict) -> None: + super().__init__(exchange, config, pairlistconfig) def short_desc(self) -> str: """ @@ -23,8 +24,12 @@ class StaticPairList(IPairList): """ return f"{self.name}: {self.whitelist}" - def refresh_pairlist(self) -> None: + def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: """ - Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new whitelist """ - self._whitelist = self.validate_whitelist(self._config['exchange']['pair_whitelist']) + return self.validate_whitelist(self._config['exchange']['pair_whitelist']) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 911bb3bda..ff601bc44 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -5,7 +5,7 @@ Provides lists as configured in config.json """ import logging -from typing import List +from typing import List, Dict from cachetools import TTLCache, cached @@ -19,18 +19,17 @@ SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] class VolumePairList(IPairList): - def __init__(self, freqtrade, config: dict) -> None: - super().__init__(freqtrade, config) - self._whitelistconf = self._config.get('pairlist', {}).get('config') - if 'number_assets' not in self._whitelistconf: + def __init__(self, exchange, config, pairlistconfig: dict) -> None: + super().__init__(exchange, config, pairlistconfig) + + if 'number_assets' not in self._pairlistconfig: raise OperationalException( f'`number_assets` not specified. Please check your configuration ' 'for "pairlist.config.number_assets"') - self._number_pairs = self._whitelistconf['number_assets'] - self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') - self._precision_filter = self._whitelistconf.get('precision_filter', True) + self._number_pairs = self._pairlistconfig['number_assets'] + self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume') - if not self._freqtrade.exchange.exchange_has('fetchTickers'): + if not self._exchange.exchange_has('fetchTickers'): raise OperationalException( 'Exchange does not support dynamic whitelist.' 'Please edit your config and restart the bot' @@ -45,14 +44,16 @@ class VolumePairList(IPairList): def short_desc(self) -> str: """ Short whitelist method description - used for startup-messages - -> Please overwrite in subclasses """ return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs." - def refresh_pairlist(self) -> None: + def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: """ - Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively - -> Please overwrite in subclasses + Filters and sorts pairlist and returns the whitelist again. + Called on each bot iteration - please use internal caching if necessary + :param pairlist: pairlist to filter or sort + :param tickers: Tickers (from exchange.get_tickers()). May be cached. + :return: new whitelist """ # Generate dynamic whitelist self._whitelist = self._gen_pair_whitelist( @@ -64,17 +65,18 @@ class VolumePairList(IPairList): Updates the whitelist with with a dynamically generated list :param base_currency: base currency as str :param key: sort key (defaults to 'quoteVolume') + :param tickers: Tickers (from exchange.get_tickers()). :return: List of pairs """ - tickers = self._freqtrade.exchange.get_tickers() + tickers = self._exchange.get_tickers() # check length so that we make sure that '/' is actually in the string tickers = [v for k, v in tickers.items() if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency and v[key] is not None)] sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) # Validate whitelist to only have active market pairs - pairs = self.validate_whitelist([s['symbol'] for s in sorted_tickers], tickers) + pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) logger.info(f"Searching {self._number_pairs} pairs: {pairs[:self._number_pairs]}") diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py new file mode 100644 index 000000000..9e92fdb6a --- /dev/null +++ b/freqtrade/pairlist/pairlistmanager.py @@ -0,0 +1,68 @@ +""" +Static List provider + +Provides lists as configured in config.json + + """ +import logging +from copy import deepcopy +from typing import List + +from freqtrade.pairlist.IPairList import IPairList +from freqtrade.resolvers import PairListResolver + +logger = logging.getLogger(__name__) + + +class PairListManager(): + + def __init__(self, exchange, config: dict) -> None: + self._exchange = exchange + self._config = config + self._whitelist = self._config['exchange'].get('pair_whitelist') + self._blacklist = self._config['exchange'].get('pair_blacklist', []) + self._pairlists: List[IPairList] = [] + + for pl in self._config.get('pairlists', [{'method': "StaticPairList"}]): + pairl = PairListResolver(pl.get('method'), + exchange, config, + pl.get('config')).pairlist + self._pairlists.append(pairl) + + @property + def whitelist(self) -> List[str]: + """ + Has the current whitelist + """ + return self._whitelist + + @property + def blacklist(self) -> List[str]: + """ + Has the current blacklist + -> no need to overwrite in subclasses + """ + return self._blacklist + + def refresh_pairlist(self) -> None: + """ + Run pairlist through all pairlists. + """ + + pairlist = self._whitelist.copy() + + # tickers should be cached to avoid calling the exchange on each call. + tickers = self._exchange.get_tickers() + for pl in self._pairlists: + pl.filter_pairlist(pairlist, tickers) + + pairlist = self._verify_blacklist(pairlist) + self._whitelist = pairlist + + def _verify_blacklist(self, pairlist: List[str]) -> List[str]: + + for pair in deepcopy(pairlist): + if pair in self.blacklist: + logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") + pairlist.remove(pair) + return pairlist diff --git a/freqtrade/resolvers/pairlist_resolver.py b/freqtrade/resolvers/pairlist_resolver.py index 0f23bb3fd..db00f6515 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -20,13 +20,15 @@ class PairListResolver(IResolver): __slots__ = ['pairlist'] - def __init__(self, pairlist_name: str, freqtrade, config: dict) -> None: + def __init__(self, pairlist_name: str, exchange, config: dict, pairlistconfig) -> None: """ Load the custom class from config parameter :param config: configuration dictionary or None """ - self.pairlist = self._load_pairlist(pairlist_name, config, kwargs={'freqtrade': freqtrade, - 'config': config}) + self.pairlist = self._load_pairlist(pairlist_name, config, + kwargs={'exchange': exchange, + 'config': config, + 'pairlistconfig': pairlistconfig}) def _load_pairlist( self, pairlist_name: str, config: dict, kwargs: dict) -> IPairList: diff --git a/freqtrade/resolvers/pairlistfilter_resolver.py b/freqtrade/resolvers/pairlistfilter_resolver.py deleted file mode 100644 index bf86d1c6c..000000000 --- a/freqtrade/resolvers/pairlistfilter_resolver.py +++ /dev/null @@ -1,53 +0,0 @@ -# pragma pylint: disable=attribute-defined-outside-init - -""" -This module load custom pairlists -""" -import logging -from pathlib import Path - -from freqtrade import OperationalException -from freqtrade.pairlist.IPairListFilter import IPairListFilter -from freqtrade.resolvers import IResolver - -logger = logging.getLogger(__name__) - - -class PairListFilterResolver(IResolver): - """ - This class contains all the logic to load custom PairList class - """ - - __slots__ = ['pairlistfilter'] - - def __init__(self, pairlist_name: str, freqtrade, config: dict) -> None: - """ - Load the custom class from config parameter - :param config: configuration dictionary or None - """ - self.pairlistfilter = self._load_pairlist(pairlist_name, config, - kwargs={'freqtrade': freqtrade, - 'config': config}) - - def _load_pairlist( - self, pairlistfilter_name: str, config: dict, kwargs: dict) -> IPairListFilter: - """ - Search and loads the specified pairlist. - :param pairlistfilter_name: name of the module to import - :param config: configuration dictionary - :param extra_dir: additional directory to search for the given pairlist - :return: PairList instance or None - """ - current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve() - - abs_paths = self.build_search_paths(config, current_path=current_path, - user_subdir=None, extra_dir=None) - - pairlist = self._load_object(paths=abs_paths, object_type=IPairListFilter, - object_name=pairlistfilter_name, kwargs=kwargs) - if pairlist: - return pairlist - raise OperationalException( - f"Impossible to load PairlistFilter '{pairlistfilter_name}'. This class does not exist " - "or contains Python code errors." - ) From b610e8c7e6b9a2d5f6c007fd446541f773fc688b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 07:05:17 +0100 Subject: [PATCH 054/157] Don't refresh tickers if they are not needed --- freqtrade/pairlist/IPairList.py | 10 +++++++++- freqtrade/pairlist/LowPriceFilter.py | 9 +++++++++ freqtrade/pairlist/PrecisionFilter.py | 9 +++++++++ freqtrade/pairlist/StaticPairList.py | 11 ++++++++++- freqtrade/pairlist/VolumePairList.py | 9 +++++++++ freqtrade/pairlist/pairlistmanager.py | 16 +++++++++++----- 6 files changed, 57 insertions(+), 7 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 845c5d01f..366a49bae 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -5,7 +5,7 @@ Provides lists as configured in config.json """ import logging -from abc import ABC, abstractmethod +from abc import ABC, abstractmethod, abstractproperty from typing import Dict, List from freqtrade.exchange import market_is_active @@ -28,6 +28,14 @@ class IPairList(ABC): """ return self.__class__.__name__ + @abstractproperty + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requries tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + @abstractmethod def short_desc(self) -> str: """ diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py index 2f4a3be75..4d4a92676 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -14,6 +14,15 @@ class LowPriceFilter(IPairList): self._low_price_percent = pairlistconfig.get('low_price_percent', 0) + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requries tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return True + def short_desc(self) -> str: """ Short whitelist method description - used for startup-messages diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index 0a590bec6..3e68ec607 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -9,6 +9,15 @@ logger = logging.getLogger(__name__) class PrecisionFilter(IPairList): + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requries tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return True + def short_desc(self) -> str: """ Short whitelist method description - used for startup-messages diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index c10dde5a6..54cd21d13 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -17,6 +17,15 @@ class StaticPairList(IPairList): def __init__(self, exchange, config, pairlistconfig: dict) -> None: super().__init__(exchange, config, pairlistconfig) + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requries tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return False + def short_desc(self) -> str: """ Short whitelist method description - used for startup-messages @@ -32,4 +41,4 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - return self.validate_whitelist(self._config['exchange']['pair_whitelist']) + return self._config['exchange']['pair_whitelist'] diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index ff601bc44..27b99ade8 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -38,6 +38,15 @@ class VolumePairList(IPairList): raise OperationalException( f'key {self._sort_key} not in {SORT_VALUES}') + @property + def needstickers(self) -> bool: + """ + Boolean property defining if tickers are necessary. + If no Pairlist requries tickers, an empty List is passed + as tickers argument to filter_pairlist + """ + return True + def _validate_keys(self, key): return key in SORT_VALUES diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 9e92fdb6a..d3532ee54 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -22,11 +22,12 @@ class PairListManager(): self._whitelist = self._config['exchange'].get('pair_whitelist') self._blacklist = self._config['exchange'].get('pair_blacklist', []) self._pairlists: List[IPairList] = [] - + self._tickers_needed = False for pl in self._config.get('pairlists', [{'method': "StaticPairList"}]): pairl = PairListResolver(pl.get('method'), exchange, config, pl.get('config')).pairlist + self._tickers_needed = pairl.needstickers or self._tickers_needed self._pairlists.append(pairl) @property @@ -52,17 +53,22 @@ class PairListManager(): pairlist = self._whitelist.copy() # tickers should be cached to avoid calling the exchange on each call. - tickers = self._exchange.get_tickers() + tickers = [] + if self._tickers_needed: + tickers = self._exchange.get_tickers() + for pl in self._pairlists: pl.filter_pairlist(pairlist, tickers) - pairlist = self._verify_blacklist(pairlist) + # Validation against blacklist happens after the pairlists to ensure blacklist is respected. + pairlist = self.verify_blacklist(pairlist, self.blacklist) self._whitelist = pairlist - def _verify_blacklist(self, pairlist: List[str]) -> List[str]: + @staticmethod + def verify_blacklist(pairlist: List[str], blacklist: List[str]) -> List[str]: for pair in deepcopy(pairlist): - if pair in self.blacklist: + if pair in blacklist: logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") pairlist.remove(pair) return pairlist From 10595862268288e7cafca1c6ed85996f906b2588 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 07:07:33 +0100 Subject: [PATCH 055/157] Small adjustments --- freqtrade/pairlist/StaticPairList.py | 4 ++-- freqtrade/pairlist/VolumePairList.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 54cd21d13..e70bd671c 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -5,7 +5,7 @@ Provides lists as configured in config.json """ import logging -from typing import List, Dict +from typing import Dict, List from freqtrade.pairlist.IPairList import IPairList @@ -31,7 +31,7 @@ class StaticPairList(IPairList): Short whitelist method description - used for startup-messages -> Please overwrite in subclasses """ - return f"{self.name}: {self.whitelist}" + return f"{self.name}" def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: """ diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 27b99ade8..ba32c8681 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -5,7 +5,7 @@ Provides lists as configured in config.json """ import logging -from typing import List, Dict +from typing import Dict, List from cachetools import TTLCache, cached @@ -54,7 +54,7 @@ class VolumePairList(IPairList): """ Short whitelist method description - used for startup-messages """ - return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs." + return f"{self.name} - top {self._pairlistconfig['number_assets']} volume pairs." def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: """ @@ -65,8 +65,7 @@ class VolumePairList(IPairList): :return: new whitelist """ # Generate dynamic whitelist - self._whitelist = self._gen_pair_whitelist( - self._config['stake_currency'], self._sort_key) + return self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) @cached(TTLCache(maxsize=1, ttl=1800)) def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: From eaf3fd80c591889d30f882ed110cd2a032b13bef Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 07:19:46 +0100 Subject: [PATCH 056/157] Allow blacklist-verification from all pairlists --- freqtrade/pairlist/IPairList.py | 13 ++++++++++++- freqtrade/pairlist/VolumePairList.py | 6 ++++-- freqtrade/pairlist/pairlistmanager.py | 15 +++------------ freqtrade/resolvers/pairlist_resolver.py | 3 ++- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 366a49bae..b666b12f2 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -6,6 +6,7 @@ Provides lists as configured in config.json """ import logging from abc import ABC, abstractmethod, abstractproperty +from copy import deepcopy from typing import Dict, List from freqtrade.exchange import market_is_active @@ -15,8 +16,9 @@ logger = logging.getLogger(__name__) class IPairList(ABC): - def __init__(self, exchange, config, pairlistconfig: dict) -> None: + def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: self._exchange = exchange + self._pairlistmanager = pairlistmanager self._config = config self._pairlistconfig = pairlistconfig @@ -54,6 +56,15 @@ class IPairList(ABC): :return: new whitelist """ + @staticmethod + def _verify_blacklist(self, pairlist: List[str]) -> List[str]: + + for pair in deepcopy(pairlist): + if pair in self._pairlistmanager.blacklist: + logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") + pairlist.remove(pair) + return pairlist + def _whitelist_for_active_markets(self, whitelist: List[str]) -> List[str]: """ Check available markets and remove pair from whitelist if necessary diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index ba32c8681..77bdf472d 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -85,7 +85,9 @@ class VolumePairList(IPairList): sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) # Validate whitelist to only have active market pairs pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) - - logger.info(f"Searching {self._number_pairs} pairs: {pairs[:self._number_pairs]}") + pairs = self._verify_blacklist(pairs) + # Limit to X number of pairs + pairs = pairs[:self._number_pairs] + logger.info(f"Searching {self._number_pairs} pairs: {pairs}") return pairs diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index d3532ee54..b1681afef 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -5,7 +5,6 @@ Provides lists as configured in config.json """ import logging -from copy import deepcopy from typing import List from freqtrade.pairlist.IPairList import IPairList @@ -25,7 +24,7 @@ class PairListManager(): self._tickers_needed = False for pl in self._config.get('pairlists', [{'method': "StaticPairList"}]): pairl = PairListResolver(pl.get('method'), - exchange, config, + exchange, self, config, pl.get('config')).pairlist self._tickers_needed = pairl.needstickers or self._tickers_needed self._pairlists.append(pairl) @@ -57,18 +56,10 @@ class PairListManager(): if self._tickers_needed: tickers = self._exchange.get_tickers() + # Process all pairlists in chain for pl in self._pairlists: - pl.filter_pairlist(pairlist, tickers) + pairlist = pl.filter_pairlist(pairlist, tickers) # Validation against blacklist happens after the pairlists to ensure blacklist is respected. pairlist = self.verify_blacklist(pairlist, self.blacklist) self._whitelist = pairlist - - @staticmethod - def verify_blacklist(pairlist: List[str], blacklist: List[str]) -> List[str]: - - for pair in deepcopy(pairlist): - if pair in blacklist: - logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") - pairlist.remove(pair) - return pairlist diff --git a/freqtrade/resolvers/pairlist_resolver.py b/freqtrade/resolvers/pairlist_resolver.py index db00f6515..a37d7dc05 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -20,13 +20,14 @@ class PairListResolver(IResolver): __slots__ = ['pairlist'] - def __init__(self, pairlist_name: str, exchange, config: dict, pairlistconfig) -> None: + def __init__(self, pairlist_name: str, exchange, pairlistmanager, config: dict, pairlistconfig) -> None: """ Load the custom class from config parameter :param config: configuration dictionary or None """ self.pairlist = self._load_pairlist(pairlist_name, config, kwargs={'exchange': exchange, + 'pairlistmanager': pairlistmanager, 'config': config, 'pairlistconfig': pairlistconfig}) From 31c7189b8b3c2cac88670e7a2204e9771bb24117 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 07:23:34 +0100 Subject: [PATCH 057/157] Verify blacklist correctly --- freqtrade/pairlist/IPairList.py | 14 +++++++++++--- freqtrade/pairlist/pairlistmanager.py | 3 ++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index b666b12f2..e14ae4187 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -57,14 +57,22 @@ class IPairList(ABC): """ @staticmethod - def _verify_blacklist(self, pairlist: List[str]) -> List[str]: - + def verify_blacklist(self, pairlist: List[str], blacklist: List[str]) -> List[str]: + """ + Verify and remove items from pairlist - returning a filtered pairlist. + """ for pair in deepcopy(pairlist): - if pair in self._pairlistmanager.blacklist: + if pair in blacklist: logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") pairlist.remove(pair) return pairlist + def _verify_blacklist(self, pairlist: List[str]) -> List[str]: + """ + Proxy method to verify_blacklist for easy access for child classes. + """ + return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist) + def _whitelist_for_active_markets(self, whitelist: List[str]) -> List[str]: """ Check available markets and remove pair from whitelist if necessary diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index b1681afef..ee6b3e37b 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -61,5 +61,6 @@ class PairListManager(): pairlist = pl.filter_pairlist(pairlist, tickers) # Validation against blacklist happens after the pairlists to ensure blacklist is respected. - pairlist = self.verify_blacklist(pairlist, self.blacklist) + pairlist = IPairList.verify_blacklist(pairlist, self.blacklist) + self._whitelist = pairlist From bf69b055ebc8b648095e0dfdbf3b461d80577ae7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 09:07:46 +0100 Subject: [PATCH 058/157] Add name getting --- freqtrade/pairlist/IPairList.py | 2 +- freqtrade/pairlist/LowPriceFilter.py | 4 ++-- freqtrade/pairlist/PrecisionFilter.py | 11 +++++------ freqtrade/pairlist/StaticPairList.py | 4 ++-- freqtrade/pairlist/VolumePairList.py | 4 ++-- freqtrade/pairlist/pairlistmanager.py | 16 ++++++++++++++-- freqtrade/resolvers/pairlist_resolver.py | 3 ++- 7 files changed, 28 insertions(+), 16 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index e14ae4187..ef9ea1d04 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -57,7 +57,7 @@ class IPairList(ABC): """ @staticmethod - def verify_blacklist(self, pairlist: List[str], blacklist: List[str]) -> List[str]: + def verify_blacklist(pairlist: List[str], blacklist: List[str]) -> List[str]: """ Verify and remove items from pairlist - returning a filtered pairlist. """ diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py index 4d4a92676..9552b56b8 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -9,8 +9,8 @@ logger = logging.getLogger(__name__) class LowPriceFilter(IPairList): - def __init__(self, exchange, config, pairlistconfig: dict) -> None: - super().__init__(exchange, config, pairlistconfig) + def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig) self._low_price_percent = pairlistconfig.get('low_price_percent', 0) diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index 3e68ec607..b18237b00 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -33,11 +33,10 @@ class PrecisionFilter(IPairList): (already cleaned to be 1 - stoploss) :return: True if the pair can stay, false if it should be removed """ - stop_price = self._freqtrade.get_target_bid(ticker["symbol"], ticker) * stoploss + stop_price = ticker['ask'] * stoploss # Adjust stop-prices to precision - sp = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], stop_price) - stop_gap_price = self._freqtrade.exchange.symbol_price_prec(ticker["symbol"], - stop_price * 0.99) + sp = self._exchange.symbol_price_prec(ticker["symbol"], stop_price) + stop_gap_price = self._exchange.symbol_price_prec(ticker["symbol"], stop_price * 0.99) logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}") if sp <= stop_gap_price: logger.info(f"Removed {ticker['symbol']} from whitelist, " @@ -49,9 +48,9 @@ class PrecisionFilter(IPairList): """ Filters and sorts pairlists and assigns and returns them again. """ - if self._freqtrade.strategy.stoploss is not None: + if self._config.get('stoploss') is not None: # Precalculate sanitized stoploss value to avoid recalculation for every pair - stoploss = 1 - abs(self._freqtrade.strategy.stoploss) + stoploss = 1 - abs(self._config.get('stoploss')) # Copy list since we're modifying this list for p in deepcopy(pairlist): ticker = [t for t in tickers if t['symbol'] == p][0] diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index e70bd671c..f663f8b02 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -14,8 +14,8 @@ logger = logging.getLogger(__name__) class StaticPairList(IPairList): - def __init__(self, exchange, config, pairlistconfig: dict) -> None: - super().__init__(exchange, config, pairlistconfig) + def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig) @property def needstickers(self) -> bool: diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 77bdf472d..610986a72 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -19,8 +19,8 @@ SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] class VolumePairList(IPairList): - def __init__(self, exchange, config, pairlistconfig: dict) -> None: - super().__init__(exchange, config, pairlistconfig) + def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig) if 'number_assets' not in self._pairlistconfig: raise OperationalException( diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index ee6b3e37b..e6b2b6103 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -5,7 +5,7 @@ Provides lists as configured in config.json """ import logging -from typing import List +from typing import Dict, List from freqtrade.pairlist.IPairList import IPairList from freqtrade.resolvers import PairListResolver @@ -44,6 +44,18 @@ class PairListManager(): """ return self._blacklist + @property + def name(self) -> str: + """ + """ + return str([p.name for p in self._pairlists]) + + def short_desc(self) -> List[Dict]: + """ + List of short_desc for each pairlist + """ + return [{p.name: p.short_desc()} for p in self._pairlists] + def refresh_pairlist(self) -> None: """ Run pairlist through all pairlists. @@ -52,7 +64,7 @@ class PairListManager(): pairlist = self._whitelist.copy() # tickers should be cached to avoid calling the exchange on each call. - tickers = [] + tickers: List[Dict] = [] if self._tickers_needed: tickers = self._exchange.get_tickers() diff --git a/freqtrade/resolvers/pairlist_resolver.py b/freqtrade/resolvers/pairlist_resolver.py index a37d7dc05..9e051fa8f 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -20,7 +20,8 @@ class PairListResolver(IResolver): __slots__ = ['pairlist'] - def __init__(self, pairlist_name: str, exchange, pairlistmanager, config: dict, pairlistconfig) -> None: + def __init__(self, pairlist_name: str, exchange, pairlistmanager, + config: dict, pairlistconfig) -> None: """ Load the custom class from config parameter :param config: configuration dictionary or None From 85beb3b6a98dc83c5d6cd405c6e260b7743cd20d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 09:31:17 +0100 Subject: [PATCH 059/157] Fix test --- tests/pairlist/test_pairlist.py | 69 ++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 23c48545e..5c718dfdf 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -7,6 +7,7 @@ import pytest from freqtrade import OperationalException from freqtrade.constants import AVAILABLE_PAIRLISTS from freqtrade.resolvers import PairListResolver +from freqtrade.pairlist.pairlistmanager import PairListManager from tests.conftest import get_patched_freqtradebot, log_has_re # whitelist, blacklist @@ -25,26 +26,41 @@ def whitelist_conf(default_conf): default_conf['exchange']['pair_blacklist'] = [ 'BLK/BTC' ] - default_conf['pairlist'] = {'method': 'StaticPairList', - 'config': {'number_assets': 5}, - 'filters': {}, - } + default_conf['pairlists'] = [ + { + "method": "VolumePairList", + "config": { + "number_assets": 5, + "sort_key": "quoteVolume", + } + }, + ] + return default_conf + +@pytest.fixture(scope="function") +def static_pl_conf(default_conf): + default_conf['pairlists'] = [ + { + "method": "StaticPairList", + }, + ] return default_conf def test_load_pairlist_noexist(mocker, markets, default_conf): - freqtradebot = get_patched_freqtradebot(mocker, default_conf) + bot = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) + plm = PairListManager(bot.exchange, default_conf) with pytest.raises(OperationalException, match=r"Impossible to load Pairlist 'NonexistingPairList'. " r"This class does not exist or contains Python code errors."): - PairListResolver('NonexistingPairList', freqtradebot, default_conf).pairlist + PairListResolver('NonexistingPairList', bot.exchange, plm, default_conf, {}) -def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf): +def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): - freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) + freqtradebot = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) freqtradebot.pairlists.refresh_pairlist() @@ -53,34 +69,33 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf): # Ensure all except those in whitelist are removed assert set(whitelist) == set(freqtradebot.pairlists.whitelist) # Ensure config dict hasn't been changed - assert (whitelist_conf['exchange']['pair_whitelist'] == + assert (static_pl_conf['exchange']['pair_whitelist'] == freqtradebot.config['exchange']['pair_whitelist']) -def test_refresh_pairlists(mocker, markets, whitelist_conf): - freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) - - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) +def test_refresh_pairlists(mocker, markets, static_pl_conf): + freqtradebot = get_patched_freqtradebot(mocker, static_pl_conf) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + exchange_has=MagicMock(return_value=True), + markets=PropertyMock(return_value=markets), + ) freqtradebot.pairlists.refresh_pairlist() # List ordered by BaseVolume whitelist = ['ETH/BTC', 'TKN/BTC'] # Ensure all except those in whitelist are removed assert set(whitelist) == set(freqtradebot.pairlists.whitelist) - assert whitelist_conf['exchange']['pair_blacklist'] == freqtradebot.pairlists.blacklist + assert static_pl_conf['exchange']['pair_blacklist'] == freqtradebot.pairlists.blacklist def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf): - whitelist_conf['pairlist'] = {'method': 'VolumePairList', - 'config': {'number_assets': 5, - 'precision_filter': False} - } mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_tickers=tickers, - exchange_has=MagicMock(return_value=True) + exchange_has=MagicMock(return_value=True), ) - freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) + bot = get_patched_freqtradebot(mocker, whitelist_conf) # Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -88,17 +103,19 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co ) # argument: use the whitelist dynamically by exchange-volume whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC'] - freqtradebot.pairlists.refresh_pairlist() + bot.pairlists.refresh_pairlist() - assert whitelist == freqtradebot.pairlists.whitelist + assert whitelist == bot.pairlists.whitelist + + whitelist_conf['pairlists'] = [{'method': 'VolumePairList', + 'config': {} + } + ] - whitelist_conf['pairlist'] = {'method': 'VolumePairList', - 'config': {} - } with pytest.raises(OperationalException, match=r'`number_assets` not specified. Please check your configuration ' r'for "pairlist.config.number_assets"'): - PairListResolver('VolumePairList', freqtradebot, whitelist_conf).pairlist + PairListManager(bot.exchange, whitelist_conf) def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): From 870966dcd0fc8e1e67b2d7b60e22e765ba8c9898 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 09:42:34 +0100 Subject: [PATCH 060/157] Fix more tests --- freqtrade/pairlist/StaticPairList.py | 2 +- tests/pairlist/test_pairlist.py | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index f663f8b02..83450c4bc 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -41,4 +41,4 @@ class StaticPairList(IPairList): :param tickers: Tickers (from exchange.get_tickers()). May be cached. :return: new whitelist """ - return self._config['exchange']['pair_whitelist'] + return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist']) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 5c718dfdf..ffebaf60f 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -16,6 +16,7 @@ from tests.conftest import get_patched_freqtradebot, log_has_re @pytest.fixture(scope="function") def whitelist_conf(default_conf): default_conf['stake_currency'] = 'BTC' + default_conf['exchange']['name'] = 'binance' default_conf['exchange']['pair_whitelist'] = [ 'ETH/BTC', 'TKN/BTC', @@ -39,13 +40,13 @@ def whitelist_conf(default_conf): @pytest.fixture(scope="function") -def static_pl_conf(default_conf): - default_conf['pairlists'] = [ +def static_pl_conf(whitelist_conf): + whitelist_conf['pairlists'] = [ { "method": "StaticPairList", }, ] - return default_conf + return whitelist_conf def test_load_pairlist_noexist(mocker, markets, default_conf): @@ -73,7 +74,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): freqtradebot.config['exchange']['pair_whitelist']) -def test_refresh_pairlists(mocker, markets, static_pl_conf): +def test_refresh_static_pairlist(mocker, markets, static_pl_conf): freqtradebot = get_patched_freqtradebot(mocker, static_pl_conf) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -119,6 +120,10 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + exchange_has=MagicMock(return_value=True), + ) freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty)) @@ -179,13 +184,15 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): - whitelist_conf['pairlist']['method'] = pairlist - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + whitelist_conf['pairlists'][0]['method'] = pairlist + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True) + ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - assert freqtrade.pairlists.name == pairlist - assert pairlist in freqtrade.pairlists.short_desc() + assert freqtrade.pairlists.name == str([pairlist]) + assert pairlist in str(freqtrade.pairlists.short_desc()) assert isinstance(freqtrade.pairlists.whitelist, list) assert isinstance(freqtrade.pairlists.blacklist, list) From d7262c0b4e7e1b2b02605da117ccd56c0bcc9da3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 13:40:36 +0100 Subject: [PATCH 061/157] Fix correct ticker type --- freqtrade/pairlist/IPairList.py | 3 +- freqtrade/pairlist/LowPriceFilter.py | 6 ++- freqtrade/pairlist/PrecisionFilter.py | 6 +-- freqtrade/pairlist/StaticPairList.py | 2 +- freqtrade/pairlist/VolumePairList.py | 21 ++++++--- freqtrade/pairlist/pairlistmanager.py | 2 +- tests/pairlist/test_pairlist.py | 68 ++++++++++++++++----------- 7 files changed, 65 insertions(+), 43 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index ef9ea1d04..4a83bc939 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -46,7 +46,7 @@ class IPairList(ABC): """ @abstractmethod - def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary @@ -97,5 +97,6 @@ class IPairList(ABC): if pair not in sanitized_whitelist: sanitized_whitelist.append(pair) + sanitized_whitelist = self._verify_blacklist(sanitized_whitelist) # We need to remove pairs that are unknown return sanitized_whitelist diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py index 9552b56b8..4e1ba52c8 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -46,7 +46,7 @@ class LowPriceFilter(IPairList): return False return True - def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlist and returns the whitelist again. @@ -57,7 +57,9 @@ class LowPriceFilter(IPairList): """ # Copy list since we're modifying this list for p in deepcopy(pairlist): - ticker = [t for t in tickers if t['symbol'] == p][0] + ticker = tickers.get(p) + if not ticker: + pairlist.remove(p) # Filter out assets which would not allow setting a stoploss if self._low_price_percent and not self._validate_ticker_lowprice(ticker): diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index b18237b00..d7b2c96ae 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -44,7 +44,7 @@ class PrecisionFilter(IPairList): return False return True - def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlists and assigns and returns them again. """ @@ -53,9 +53,9 @@ class PrecisionFilter(IPairList): stoploss = 1 - abs(self._config.get('stoploss')) # Copy list since we're modifying this list for p in deepcopy(pairlist): - ticker = [t for t in tickers if t['symbol'] == p][0] + ticker = tickers.get(p) # Filter out assets which would not allow setting a stoploss - if (stoploss and not self._validate_precision_filter(ticker, stoploss)): + if not ticker or (stoploss and not self._validate_precision_filter(ticker, stoploss)): pairlist.remove(p) continue diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index 83450c4bc..a7b71875c 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -33,7 +33,7 @@ class StaticPairList(IPairList): """ return f"{self.name}" - def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 610986a72..e6ff69daf 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -5,10 +5,9 @@ Provides lists as configured in config.json """ import logging +from datetime import datetime from typing import Dict, List -from cachetools import TTLCache, cached - from freqtrade import OperationalException from freqtrade.pairlist.IPairList import IPairList @@ -28,6 +27,7 @@ class VolumePairList(IPairList): 'for "pairlist.config.number_assets"') self._number_pairs = self._pairlistconfig['number_assets'] self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume') + self._ttl = self._pairlistconfig.get('ttl', 1800) if not self._exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -37,6 +37,7 @@ class VolumePairList(IPairList): if not self._validate_keys(self._sort_key): raise OperationalException( f'key {self._sort_key} not in {SORT_VALUES}') + self._last_refresh = 0 @property def needstickers(self) -> bool: @@ -56,7 +57,7 @@ class VolumePairList(IPairList): """ return f"{self.name} - top {self._pairlistconfig['number_assets']} volume pairs." - def filter_pairlist(self, pairlist: List[str], tickers: List[Dict]) -> List[str]: + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: """ Filters and sorts pairlist and returns the whitelist again. Called on each bot iteration - please use internal caching if necessary @@ -65,10 +66,17 @@ class VolumePairList(IPairList): :return: new whitelist """ # Generate dynamic whitelist - return self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) + if self._last_refresh + self._ttl < datetime.now().timestamp(): + self._last_refresh = datetime.now().timestamp() + return self._gen_pair_whitelist(pairlist, + tickers, + self._config['stake_currency'], + self._sort_key, + ) + else: + return pairlist - @cached(TTLCache(maxsize=1, ttl=1800)) - def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: + def _gen_pair_whitelist(self, pairlist, tickers, base_currency: str, key: str) -> List[str]: """ Updates the whitelist with with a dynamically generated list :param base_currency: base currency as str @@ -77,7 +85,6 @@ class VolumePairList(IPairList): :return: List of pairs """ - tickers = self._exchange.get_tickers() # check length so that we make sure that '/' is actually in the string tickers = [v for k, v in tickers.items() if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index e6b2b6103..03451e725 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -64,7 +64,7 @@ class PairListManager(): pairlist = self._whitelist.copy() # tickers should be cached to avoid calling the exchange on each call. - tickers: List[Dict] = [] + tickers: Dict = {} if self._tickers_needed: tickers = self._exchange.get_tickers() diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index ffebaf60f..5ad7fdf5a 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -16,7 +16,6 @@ from tests.conftest import get_patched_freqtradebot, log_has_re @pytest.fixture(scope="function") def whitelist_conf(default_conf): default_conf['stake_currency'] = 'BTC' - default_conf['exchange']['name'] = 'binance' default_conf['exchange']['pair_whitelist'] = [ 'ETH/BTC', 'TKN/BTC', @@ -137,31 +136,37 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): @pytest.mark.parametrize("filters,base_currency,key,whitelist_result", [ - ({}, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), - ({}, "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']), - ({}, "USDT", "quoteVolume", ['ETH/USDT']), - ({}, "ETH", "quoteVolume", []), - ({"PrecisionFilter": {}}, "BTC", "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']), - ({"PrecisionFilter": {}}, "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC", 'FUEL/BTC']), - ({"LowPriceFilter": {"low_price_percent": 0.03}}, "BTC", - "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'FUEL/BTC']), + ([], "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + ([], "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']), + ([], "USDT", "quoteVolume", ['ETH/USDT']), + ([], "ETH", "quoteVolume", []), + ([{"method": "PrecisionFilter"}], "BTC", + "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']), + ([{"method": "PrecisionFilter"}], + "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC", 'FUEL/BTC']), + ([{"method": "LowPriceFilter", "config": {"low_price_percent": 0.03}}], + "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'FUEL/BTC']), # Hot is removed by precision_filter, Fuel by low_price_filter. - ({"PrecisionFilter": {}, "LowPriceFilter": {"low_price_percent": 0.02}}, - "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC']), -]) + ([{"method": "PrecisionFilter"}, + {"method": "LowPriceFilter", "config": {"low_price_percent": 0.02}} + ], "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC'])]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, filters, base_currency, key, whitelist_result, caplog) -> None: - whitelist_conf['pairlist']['method'] = 'VolumePairList' - whitelist_conf['pairlist']['filters'] = filters + whitelist_conf['pairlists'].extend(filters) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=shitcoinmarkets)) - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) + + mocker.patch.multiple('freqtrade.exchange.Exchange', + get_tickers=tickers, + markets=PropertyMock(return_value=shitcoinmarkets), + ) freqtrade.config['stake_currency'] = base_currency - whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key) + freqtrade.pairlists.refresh_pairlist() + whitelist = freqtrade.pairlists.whitelist + assert sorted(whitelist) == sorted(whitelist_result) if 'PrecisionFilter' in filters: assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' @@ -172,11 +177,14 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: - default_conf['pairlist'] = {'method': 'VolumePairList', - 'config': {'number_assets': 10} - } - mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False)) + default_conf['pairlists'] = [{'method': 'VolumePairList', + 'config': {'number_assets': 10} + }] + + mocker.patch.multiple('freqtrade.exchange.Exchange', + get_tickers=tickers, + exchange_has=MagicMock(return_value=False), + ) with pytest.raises(OperationalException): get_patched_freqtradebot(mocker, default_conf) @@ -202,18 +210,22 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): (['ETH/BTC', 'TKN/BTC'], ""), (['ETH/BTC', 'TKN/BTC', 'TRX/ETH'], "is not compatible with exchange"), # TRX/ETH wrong stake (['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), # BCH/BTC not available - (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "is not compatible with exchange"), # BLK/BTC in blacklist + (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "in your blacklist. Removing "), # BLK/BTC in blacklist (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active") # BTT/BTC is inactive ]) def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, - log_message): - whitelist_conf['pairlist']['method'] = pairlist - mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)) - mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + log_message, tickers): + whitelist_conf['pairlists'][0]['method'] = pairlist + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) caplog.clear() - new_whitelist = freqtrade.pairlists._whitelist_for_active_markets(whitelist) + # Assign starting whitelist + new_whitelist = freqtrade.pairlists._pairlists[0]._whitelist_for_active_markets(whitelist) assert set(new_whitelist) == set(['ETH/BTC', 'TKN/BTC']) assert log_message in caplog.text From c3b4a4dde1e2421bbd3db783b463ab6b1e9fbee9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 13:59:19 +0100 Subject: [PATCH 062/157] Update sample configurations --- config.json.example | 5 ++++- config_binance.json.example | 5 +++++ config_full.json.example | 23 ++++++++++++++--------- config_kraken.json.example | 5 +++++ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/config.json.example b/config.json.example index 9a6dafd04..a2add358f 100644 --- a/config.json.example +++ b/config.json.example @@ -52,6 +52,9 @@ "DOGE/BTC" ] }, + "pairlists": [ + {"method": "StaticPairList"} + ], "edge": { "enabled": false, "process_throttle_secs": 3600, @@ -68,7 +71,7 @@ "remove_pumps": false }, "telegram": { - "enabled": true, + "enabled": false, "token": "your_telegram_token", "chat_id": "your_telegram_chat_id" }, diff --git a/config_binance.json.example b/config_binance.json.example index 58817a78e..aa36ed035 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -54,6 +54,11 @@ "BNB/BTC" ] }, + "pairlists": [ + { + "method": "StaticPairList" + } + ], "edge": { "enabled": false, "process_throttle_secs": 3600, diff --git a/config_full.json.example b/config_full.json.example index 9cec8fcb8..2bc2cde9e 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -50,17 +50,22 @@ "buy": "gtc", "sell": "gtc" }, - "pairlist": { - "method": "VolumePairList", - "config": { - "number_assets": 20, - "sort_key": "quoteVolume", + "pairlists": [ + {"method": "StaticPairList"}, + { + "method": "VolumePairList", + "config": { + "number_assets": 20, + "sort_key": "quoteVolume" + } }, - "filters":{ - "PrecisionFilter": {}, - "LowPriceFilter": {"low_price_percent": 0.01} + {"method": "PrecisionFilter"}, + {"method": "LowPriceFilter", + "config": { + "low_price_percent": 0.01 + } } - }, + ], "exchange": { "name": "bittrex", "sandbox": false, diff --git a/config_kraken.json.example b/config_kraken.json.example index 5a36941d8..9ca2ca065 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -46,6 +46,11 @@ ] }, + "pairlists": [ + { + "method": "StaticPairList" + } + ], "edge": { "enabled": false, "process_throttle_secs": 3600, From 37985310d5a748302ec0e4d974f66b7efbaddab6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 13:59:35 +0100 Subject: [PATCH 063/157] remove cachetools dependency --- requirements-common.txt | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/requirements-common.txt b/requirements-common.txt index 64a43ee62..4793d25f6 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -4,7 +4,6 @@ ccxt==1.18.1346 SQLAlchemy==1.3.10 python-telegram-bot==12.2.0 arrow==0.15.2 -cachetools==3.1.1 requests==2.22.0 urllib3==1.25.6 wrapt==1.11.2 diff --git a/setup.py b/setup.py index 50b8eee9c..781a5d138 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,6 @@ setup(name='freqtrade', 'SQLAlchemy', 'python-telegram-bot', 'arrow', - 'cachetools', 'requests', 'urllib3', 'wrapt', From c74d76627546fd2ea84149abc629bde8161e7414 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 14:00:32 +0100 Subject: [PATCH 064/157] move from name to name_list --- freqtrade/pairlist/VolumePairList.py | 2 +- freqtrade/pairlist/pairlistmanager.py | 13 ++++++++++--- freqtrade/rpc/rpc.py | 4 ++-- tests/pairlist/test_pairlist.py | 2 +- tests/rpc/test_rpc.py | 17 ++++++++++------- tests/rpc/test_rpc_apiserver.py | 6 +++--- 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index e6ff69daf..902f7abd4 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -67,7 +67,7 @@ class VolumePairList(IPairList): """ # Generate dynamic whitelist if self._last_refresh + self._ttl < datetime.now().timestamp(): - self._last_refresh = datetime.now().timestamp() + self._last_refresh = int(datetime.now().timestamp()) return self._gen_pair_whitelist(pairlist, tickers, self._config['stake_currency'], diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 03451e725..09e024497 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -7,6 +7,7 @@ Provides lists as configured in config.json import logging from typing import Dict, List +from freqtrade import OperationalException from freqtrade.pairlist.IPairList import IPairList from freqtrade.resolvers import PairListResolver @@ -22,12 +23,17 @@ class PairListManager(): self._blacklist = self._config['exchange'].get('pair_blacklist', []) self._pairlists: List[IPairList] = [] self._tickers_needed = False - for pl in self._config.get('pairlists', [{'method': "StaticPairList"}]): + pairlists = self._config.get('pairlists', None) + if not pairlists: + pairlists = [{'method': "StaticPairList"}] + for pl in pairlists: pairl = PairListResolver(pl.get('method'), exchange, self, config, pl.get('config')).pairlist self._tickers_needed = pairl.needstickers or self._tickers_needed self._pairlists.append(pairl) + if not self._pairlists: + raise OperationalException("No Pairlist defined!!") @property def whitelist(self) -> List[str]: @@ -45,10 +51,11 @@ class PairListManager(): return self._blacklist @property - def name(self) -> str: + def name_list(self) -> List[str]: """ + Get list of loaded pairlists names """ - return str([p.name for p in self._pairlists]) + return [p.name for p in self._pairlists] def short_desc(self) -> List[Dict]: """ diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f994ac006..2de73ac25 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -469,7 +469,7 @@ class RPC: def _rpc_whitelist(self) -> Dict: """ Returns the currently active whitelist""" - res = {'method': self._freqtrade.pairlists.name, + res = {'method': self._freqtrade.pairlists.name_list, 'length': len(self._freqtrade.active_pair_whitelist), 'whitelist': self._freqtrade.active_pair_whitelist } @@ -484,7 +484,7 @@ class RPC: and pair not in self._freqtrade.pairlists.blacklist): self._freqtrade.pairlists.blacklist.append(pair) - res = {'method': self._freqtrade.pairlists.name, + res = {'method': self._freqtrade.pairlists.name_list, 'length': len(self._freqtrade.pairlists.blacklist), 'blacklist': self._freqtrade.pairlists.blacklist, } diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 5ad7fdf5a..bca3f4aea 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -199,7 +199,7 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): ) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) - assert freqtrade.pairlists.name == str([pairlist]) + assert freqtrade.pairlists.name_list == [pairlist] assert pairlist in str(freqtrade.pairlists.short_desc()) assert isinstance(freqtrade.pairlists.whitelist, list) assert isinstance(freqtrade.pairlists.blacklist, list) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index df2261c1f..8747fe6ff 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -719,21 +719,23 @@ def test_rpc_whitelist(mocker, default_conf) -> None: freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) ret = rpc._rpc_whitelist() - assert ret['method'] == 'StaticPairList' + assert len(ret['method']) == 1 + assert 'StaticPairList' in ret['method'] assert ret['whitelist'] == default_conf['exchange']['pair_whitelist'] def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: - default_conf['pairlist'] = {'method': 'VolumePairList', - 'config': {'number_assets': 4} - } + default_conf['pairlists'] = [{'method': 'VolumePairList', + 'config': {'number_assets': 4} + }] mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) ret = rpc._rpc_whitelist() - assert ret['method'] == 'VolumePairList' + assert len(ret['method']) == 1 + assert 'VolumePairList' in ret['method'] assert ret['length'] == 4 assert ret['whitelist'] == default_conf['exchange']['pair_whitelist'] @@ -744,13 +746,14 @@ def test_rpc_blacklist(mocker, default_conf) -> None: freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) ret = rpc._rpc_blacklist(None) - assert ret['method'] == 'StaticPairList' + assert len(ret['method']) == 1 + assert 'StaticPairList' in ret['method'] assert len(ret['blacklist']) == 2 assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC'] ret = rpc._rpc_blacklist(["ETH/BTC"]) - assert ret['method'] == 'StaticPairList' + assert 'StaticPairList' in ret['method'] assert len(ret['blacklist']) == 3 assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index b572a0514..aa5054314 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -456,7 +456,7 @@ def test_api_blacklist(botclient, mocker): assert_response(rc) assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"], "length": 2, - "method": "StaticPairList"} + "method": ["StaticPairList"]} # Add ETH/BTC to blacklist rc = client_post(client, f"{BASE_URI}/blacklist", @@ -464,7 +464,7 @@ def test_api_blacklist(botclient, mocker): assert_response(rc) assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"], "length": 3, - "method": "StaticPairList"} + "method": ["StaticPairList"]} def test_api_whitelist(botclient): @@ -474,7 +474,7 @@ def test_api_whitelist(botclient): assert_response(rc) assert rc.json == {"whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'], "length": 4, - "method": "StaticPairList"} + "method": ["StaticPairList"]} def test_api_forcebuy(botclient, mocker, fee): From 25cb935eeedce1e19afc2383953301b6fe79ab6e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 14:15:47 +0100 Subject: [PATCH 065/157] Some more adjustments for new pairlist --- freqtrade/configuration/config_validation.py | 7 ++++--- freqtrade/configuration/configuration.py | 3 +++ freqtrade/configuration/deprecated_settings.py | 11 ++++++++++- tests/conftest.py | 3 +++ tests/rpc/test_rpc_telegram.py | 14 +++++++------- tests/test_configuration.py | 8 +++++--- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 93d93263f..21086c913 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -121,6 +121,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None: if conf.get('runmode', RunMode.OTHER) in [RunMode.OTHER, RunMode.PLOT]: return - if (conf.get('pairlist', {}).get('method', 'StaticPairList') == 'StaticPairList' - and not conf.get('exchange', {}).get('pair_whitelist')): - raise OperationalException("StaticPairList requires pair_whitelist to be set.") + for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]): + if (pl.get('method') == 'StaticPairList' + and not conf.get('exchange', {}).get('pair_whitelist')): + raise OperationalException("StaticPairList requires pair_whitelist to be set.") diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index be1c7ab4e..b8995edf9 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -81,6 +81,9 @@ class Configuration: if 'ask_strategy' not in config: config['ask_strategy'] = {} + if 'pairlists' not in config: + config['pairlists'] = [] + # validate configuration before returning logger.info('Validating configuration ...') validate_config_schema(config) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 8471028aa..3aec85ae2 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -58,9 +58,18 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal', 'experimental', 'ignore_roi_if_buy_signal') + if config.get('pairlist', {}).get("method") == 'VolumePairList': + logger.warning( + "DEPRECATED: " + f"Using VolumePairList in pairlist is deprecated and must be moved to pairlists. " + "Please refer to the docs on configuration details") + config['pairlists'].append({'method': 'VolumePairList', + 'config': config.get('pairlist', {}).get('config') + }) + if config.get('pairlist', {}).get('config', {}).get('precision_filter'): logger.warning( "DEPRECATED: " f"Using precision_filter setting is deprecated and has been replaced by" "PrecisionFilter. Please refer to the docs on configuration details") - config['pairlist'].update({'filters': {'PrecisionFilter': {}}}) + config['pairlists'].append({'method': 'PrecisionFilter'}) diff --git a/tests/conftest.py b/tests/conftest.py index d551596f0..b2c76baec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -242,6 +242,9 @@ def default_conf(testdatadir): "HOT/BTC", ] }, + "pairlists": [ + {"method": "StaticPairList"} + ], "telegram": { "enabled": True, "token": "token", diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 766511d2d..bb9d88658 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1050,8 +1050,8 @@ def test_whitelist_static(default_conf, update, mocker) -> None: telegram._whitelist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert ('Using whitelist `StaticPairList` with 4 pairs\n`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`' - in msg_mock.call_args_list[0][0][0]) + assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" + "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0]) def test_whitelist_dynamic(default_conf, update, mocker) -> None: @@ -1062,17 +1062,17 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None: _send_msg=msg_mock ) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - default_conf['pairlist'] = {'method': 'VolumePairList', - 'config': {'number_assets': 4} - } + default_conf['pairlists'] = [{'method': 'VolumePairList', + 'config': {'number_assets': 4} + }] freqtradebot = get_patched_freqtradebot(mocker, default_conf) telegram = Telegram(freqtradebot) telegram._whitelist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert ('Using whitelist `VolumePairList` with 4 pairs\n`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`' - in msg_mock.call_args_list[0][0][0]) + assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" + "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0]) def test_blacklist_static(default_conf, update, mocker) -> None: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 258088925..6cfae01c6 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -743,9 +743,9 @@ def test_validate_whitelist(default_conf): conf = deepcopy(default_conf) - conf.update({"pairlist": { + conf.update({"pairlists": [{ "method": "VolumePairList", - }}) + }]}) # Dynamic whitelist should not care about pair_whitelist validate_config_consistency(conf) del conf['exchange']['pair_whitelist'] @@ -963,14 +963,16 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca assert default_conf[setting[0]][setting[1]] == setting[5] -def test_process_deprecated_setting_precision_filter(mocker, default_conf, caplog): +def test_process_deprecated_setting_pairlists(mocker, default_conf, caplog): patched_configuration_load_config_file(mocker, default_conf) default_conf.update({'pairlist': { + 'method': 'VolumePairList', 'config': {'precision_filter': True} }}) process_temporary_deprecated_settings(default_conf) assert log_has_re(r'DEPRECATED.*precision_filter.*', caplog) + assert log_has_re(r'DEPRECATED.*in pairlist is deprecated and must be moved*', caplog) def test_check_conflicting_settings(mocker, default_conf, caplog): From ed0c7a6aaf80c4c92df7480577bcbe6d10a66d02 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 14:16:11 +0100 Subject: [PATCH 066/157] Update configschema to fit new pairlists approach --- freqtrade/constants.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 5fdd45916..c98c32d4c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -20,7 +20,7 @@ REQUIRED_ORDERTIF = ['buy', 'sell'] REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] -AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] +AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'LowPriceFilter'] DRY_RUN_WALLET = 999.9 MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons @@ -151,13 +151,16 @@ CONF_SCHEMA = { 'block_bad_exchanges': {'type': 'boolean'} } }, - 'pairlist': { - 'type': 'object', - 'properties': { - 'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, - 'config': {'type': 'object'} - }, - 'required': ['method'] + 'pairlists': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, + 'config': {'type': 'object'} + }, + 'required': ['method'], + } }, 'telegram': { 'type': 'object', From 02b9da8aba530bc0f9115401f634108d2bdb947f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 14:39:28 +0100 Subject: [PATCH 067/157] Update documentation --- config_full.json.example | 3 ++- docs/configuration.md | 46 ++++++++++++++++++++++++---------------- docs/developer.md | 29 ++++++++++++++----------- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 2bc2cde9e..ba53f47d6 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -56,7 +56,8 @@ "method": "VolumePairList", "config": { "number_assets": 20, - "sort_key": "quoteVolume" + "sort_key": "quoteVolume", + "ttl": 1800 } }, {"method": "PrecisionFilter"}, diff --git a/docs/configuration.md b/docs/configuration.md index 39fb4b8ac..8f62708d1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -82,8 +82,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded. | `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.block_bad_exchanges` | true | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. -| `pairlist.method` | StaticPairList | Use static or dynamic volume-based pairlist. [More information below](#dynamic-pairlists). -| `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists). +| `pairlists` | StaticPairList | Define one or more pairlists to be used. [More information below](#dynamic-pairlists). | `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram. | `telegram.token` | token | Your Telegram bot token. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.*** | `telegram.chat_id` | chat_id | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.*** @@ -382,41 +381,50 @@ The valid values are: Pairlists define the list of pairs that the bot should trade. There are [`StaticPairList`](#static-pair-list) and dynamic Whitelists available. -In addition to pairlists, [pairlist filters](#available-pairlist-filters) can be configured, which remove certain assets. -These Filters work with all Pairlist providers and are applied in the sequence they occur. +[`PrecisionFilter`](#precision-filter) and [`LowPriceFilter`](#low-price-pair-filter) act as filters, removing low-value pairs. + +All pairlists can be chained, and a combination of all pairlists will become your new whitelist. + +Inactive markets and blacklisted pairs are always removed from the resulting `pair_whitelist`. ### Available Pairlists * [`StaticPairList`](#static-pair-list) (default, if not configured differently) * [`VolumePairList`](#volume-pair-list) +* [`PrecisionFilter`](#precision-filter) +* [`LowPriceFilter`](#low-price-pair-filter) #### Static Pair List -By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. Inactive markets and blacklisted pairs are removed from the pair_whitelist. +By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`. +```json +"pairlists": [ + {"method": "StaticPairList"} + ], +``` + #### Volume Pair List `VolumePairList` selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume` and defaults to `quoteVolume`. `VolumePairList` does not consider `pair_whitelist`, but selects the top assets from all available markets (with matching stake-currency) on the exchange. -Pairs in `pair_blacklist` are not considered for `VolumePairList`, even if all other filters would match. + +`ttl` allows setting the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). ```json -"pairlist": { +"pairlists": [{ "method": "VolumePairList", "config": { "number_assets": 20, "sort_key": "quoteVolume", - }, + "ttl": 1800, + } +], ``` -### Available Pairlist Filters - -* [`PrecisionFilter`](#precision-filter) -* [`LowPriceFilter`](#low-price-pair-filter) - #### Precision Filter Filters low-value coins which would not allow setting a stoploss. @@ -440,17 +448,19 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, "pair_whitelist": [], "pair_blacklist": ["BNB/BTC"] }, -"pairlist": { +"pairlists": [ + { "method": "VolumePairList", "config": { "number_assets": 20, "sort_key": "quoteVolume", }, - "filters":{ - "PrecisionFilter": {}, - "LowPriceFilter": {"low_price_percent": 0.01} - } }, + {"method": "PrecisionFilter"}, + {"method": "LowPriceFilter", + "config": {"low_price_percent": 0.01} + } + }], ``` ## Switch to Dry-run mode diff --git a/docs/developer.md b/docs/developer.md index 346578c2e..e63d970e2 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -46,15 +46,18 @@ def test_method_to_test(caplog): The fastest and easiest way to start up is to use docker-compose.develop which gives developers the ability to start the bot up with all the required dependencies, *without* needing to install any freqtrade specific dependencies on your local machine. #### Install + * [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) * [docker](https://docs.docker.com/install/) * [docker-compose](https://docs.docker.com/compose/install/) #### Starting the bot ##### Use the develop dockerfile + ``` bash rm docker-compose.yml && mv docker-compose.develop.yml docker-compose.yml ``` + #### Docker Compose ##### Starting @@ -62,9 +65,11 @@ rm docker-compose.yml && mv docker-compose.develop.yml docker-compose.yml ``` bash docker-compose up ``` + ![Docker compose up](https://user-images.githubusercontent.com/419355/65456322-47f63a80-de06-11e9-90c6-3c74d1bad0b8.png) ##### Rebuilding + ``` bash docker-compose build ``` @@ -77,8 +82,8 @@ that can be effected by `docker-compose up` or `docker-compose run freqtrade_dev ``` bash docker-compose exec freqtrade_develop /bin/bash ``` -![image](https://user-images.githubusercontent.com/419355/65456522-ba671a80-de06-11e9-9598-df9ca0d8dcac.png) +![image](https://user-images.githubusercontent.com/419355/65456522-ba671a80-de06-11e9-9598-df9ca0d8dcac.png) ## Modules @@ -95,7 +100,7 @@ This is a simple provider, which however serves as a good example on how to star Next, modify the classname of the provider (ideally align this with the Filename). -The base-class provides the an instance of the bot (`self._freqtrade`), as well as the configuration (`self._config`), and initiates both `_blacklist` and `_whitelist`. +The base-class provides an instance of the exchange (`self._exchange`) the pairlist manager (`self._pairlistmanager`), as well as the main configuration (`self._config`) and the pairlist dedicated configuration (`self._pairlistconfig`). ```python self._freqtrade = freqtrade @@ -104,10 +109,9 @@ The base-class provides the an instance of the bot (`self._freqtrade`), as well self._blacklist = self._config['exchange'].get('pair_blacklist', []) ``` - Now, let's step through the methods which require actions: -#### configuration +#### Pairlist configuration Configuration for PairListProvider is done in the bot configuration file in the element `"pairlist"`. This Pairlist-object may contain a `"config"` dict with additional configurations for the configured pairlist. @@ -120,29 +124,30 @@ Additional elements can be configured as needed. `VolumePairList` uses `"sort_ke Returns a description used for Telegram messages. This should contain the name of the Provider, as well as a short description containing the number of assets. Please follow the format `"PairlistName - top/bottom X pairs"`. -#### refresh_pairlist +#### filter_pairlist Override this method and run all calculations needed in this method. This is called with each iteration of the bot - so consider implementing caching for compute/network heavy calculations. -Assign the resulting whiteslist to `self._whitelist` and `self._blacklist` respectively. These will then be used to run the bot in this iteration. Pairs with open trades will be added to the whitelist to have the sell-methods run correctly. +It get's passed a pairlist (which can be the result of previous pairlists) as well as `tickers`, a pre-fetched version of `get_tickers()`. -Please also run `self.validate_whitelist(pairs, tickers)` (tickers is optional, but should be passed when you're using tickers anyway) and to check and remove pairs with inactive markets. This function is available in the Parent class (`StaticPairList`) and should ideally not be overwritten. +It must return the resulting pairlist (which may then be passed into the next pairlist filter). + +Validations are optional, the parent class exposes a `_verify_blacklist(pairlist)` and `_whitelist_for_active_markets(pairlist)` to do default filters. Use this if you limit your result to a certain number of pairs - so the endresult is not shorter than expected. ##### sample ``` python - def refresh_pairlist(self) -> None: + def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: # Generate dynamic whitelist - pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) - # Validate whitelist to only have active market pairs - self._whitelist = self.validate_whitelist(pairs)[:self._number_pairs] + pairs = self._calculate_pairlist(pairlist, tickers) + return pairs ``` #### _gen_pair_whitelist This is a simple method used by `VolumePairList` - however serves as a good example. -It implements caching (`@cached(TTLCache(maxsize=1, ttl=1800))`) as well as a configuration option to allow different (but similar) strategies to work with the same PairListProvider. +In VolumePairList, this implements different methods of sorting, does early validation so only the expected number of pairs is returned. ## Implement a new Exchange (WIP) From a01b34a004baa585cf3a9045d8aeb0374e076a83 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 14:44:39 +0100 Subject: [PATCH 068/157] tests --- freqtrade/pairlist/IPairList.py | 4 ++-- tests/pairlist/test_pairlist.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 4a83bc939..fc4187856 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -73,7 +73,7 @@ class IPairList(ABC): """ return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist) - def _whitelist_for_active_markets(self, whitelist: List[str]) -> List[str]: + def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]: """ Check available markets and remove pair from whitelist if necessary :param whitelist: the sorted list of pairs the user might want to trade @@ -83,7 +83,7 @@ class IPairList(ABC): markets = self._exchange.markets sanitized_whitelist: List[str] = [] - for pair in whitelist: + for pair in pairlist: # pair is not in the generated dynamic market or has the wrong stake currency if (pair not in markets or not pair.endswith(self._config['stake_currency'])): logger.warning(f"Pair {pair} is not compatible with exchange " diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index bca3f4aea..d19c18715 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -229,3 +229,31 @@ def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist assert set(new_whitelist) == set(['ETH/BTC', 'TKN/BTC']) assert log_message in caplog.text + + +def test_volumepairlist_invalid_sortvalue(mocker, markets, whitelist_conf): + whitelist_conf['pairlists'][0]['config'].update({"sort_key": "asdf"}) + + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + with pytest.raises(OperationalException, + match=r"key asdf not in .*"): + get_patched_freqtradebot(mocker, whitelist_conf) + + +def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): + + mocker.patch.multiple('freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + exchange_has=MagicMock(return_value=True), + get_tickers=tickers + ) + bot = get_patched_freqtradebot(mocker, whitelist_conf) + assert bot.pairlists._pairlists[0]._last_refresh == 0 + + bot.pairlists.refresh_pairlist() + + assert bot.pairlists._pairlists[0]._last_refresh != 0 + lrf = bot.pairlists._pairlists[0]._last_refresh + bot.pairlists.refresh_pairlist() + # Time should not be updated. + assert bot.pairlists._pairlists[0]._last_refresh == lrf From ae356493665270b557e93099970e78377a8b185d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 14:49:25 +0100 Subject: [PATCH 069/157] improve pairlistmanager errorhandling --- freqtrade/pairlist/pairlistmanager.py | 12 +++++++----- tests/pairlist/test_pairlist.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 09e024497..f29f89abf 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -23,17 +23,19 @@ class PairListManager(): self._blacklist = self._config['exchange'].get('pair_blacklist', []) self._pairlists: List[IPairList] = [] self._tickers_needed = False - pairlists = self._config.get('pairlists', None) - if not pairlists: - pairlists = [{'method': "StaticPairList"}] - for pl in pairlists: + + for pl in self._config.get('pairlists', None): + if 'method' not in pl: + logger.warning(f"No method in {pl}") + continue pairl = PairListResolver(pl.get('method'), exchange, self, config, pl.get('config')).pairlist self._tickers_needed = pairl.needstickers or self._tickers_needed self._pairlists.append(pairl) + if not self._pairlists: - raise OperationalException("No Pairlist defined!!") + raise OperationalException("No Pairlist defined!") @property def whitelist(self) -> List[str]: diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index d19c18715..017f78561 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -257,3 +257,19 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): bot.pairlists.refresh_pairlist() # Time should not be updated. assert bot.pairlists._pairlists[0]._last_refresh == lrf + + +def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog): + del whitelist_conf['pairlists'][0]['method'] + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + with pytest.raises(OperationalException, + match=r"No Pairlist defined!"): + get_patched_freqtradebot(mocker, whitelist_conf) + assert log_has_re("No method in .*", caplog) + + whitelist_conf['pairlists'] = [] + + with pytest.raises(OperationalException, + match=r"No Pairlist defined!"): + get_patched_freqtradebot(mocker, whitelist_conf) + From 7ff61f12e99ac67fa61f33e87e06d76e6d303008 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 15:04:04 +0100 Subject: [PATCH 070/157] pass pairlist position into the pairlists --- freqtrade/pairlist/IPairList.py | 11 ++++++++++- freqtrade/pairlist/LowPriceFilter.py | 5 +++-- freqtrade/pairlist/StaticPairList.py | 3 --- freqtrade/pairlist/VolumePairList.py | 22 +++++++++++++++------- freqtrade/pairlist/pairlistmanager.py | 11 +++++++---- freqtrade/resolvers/pairlist_resolver.py | 5 +++-- tests/pairlist/test_pairlist.py | 2 +- 7 files changed, 39 insertions(+), 20 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index fc4187856..231755cb0 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -16,11 +16,20 @@ logger = logging.getLogger(__name__) class IPairList(ABC): - def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: + def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict, + pairlist_pos: int) -> None: + """ + :param exchange: Exchange instance + :param pairlistmanager: Instanciating Pairlist manager + :param config: Global bot configuration + :param pairlistconfig: Configuration for this pairlist - can be empty. + :param pairlist_pos: Position of the filter in the pairlist-filter-list + """ self._exchange = exchange self._pairlistmanager = pairlistmanager self._config = config self._pairlistconfig = pairlistconfig + self._pairlist_pos = pairlist_pos @property def name(self) -> str: diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py index 4e1ba52c8..83b6a85e6 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -9,8 +9,9 @@ logger = logging.getLogger(__name__) class LowPriceFilter(IPairList): - def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig) + def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict, + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) self._low_price_percent = pairlistconfig.get('low_price_percent', 0) diff --git a/freqtrade/pairlist/StaticPairList.py b/freqtrade/pairlist/StaticPairList.py index a7b71875c..0050fbd5c 100644 --- a/freqtrade/pairlist/StaticPairList.py +++ b/freqtrade/pairlist/StaticPairList.py @@ -14,9 +14,6 @@ logger = logging.getLogger(__name__) class StaticPairList(IPairList): - def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig) - @property def needstickers(self) -> bool: """ diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 902f7abd4..708c8d7c2 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -18,8 +18,9 @@ SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] class VolumePairList(IPairList): - def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict) -> None: - super().__init__(exchange, pairlistmanager, config, pairlistconfig) + def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict, + pairlist_pos: int) -> None: + super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) if 'number_assets' not in self._pairlistconfig: raise OperationalException( @@ -85,11 +86,18 @@ class VolumePairList(IPairList): :return: List of pairs """ - # check length so that we make sure that '/' is actually in the string - tickers = [v for k, v in tickers.items() - if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency - and v[key] is not None)] - sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) + if self._pairlist_pos == 0: + # If VolumePairList is the first in the list, use fresh pairlist + # check length so that we make sure that '/' is actually in the string + filtered_tickers = [v for k, v in tickers.items() + if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency + and v[key] is not None)] + else: + # If other pairlist is in front, use the incomming pairlist. + filtered_tickers = [v for k, v in tickers.items() if k in pairlist] + + sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[key]) + # Validate whitelist to only have active market pairs pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) pairs = self._verify_blacklist(pairs) diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index f29f89abf..309ada094 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -23,14 +23,17 @@ class PairListManager(): self._blacklist = self._config['exchange'].get('pair_blacklist', []) self._pairlists: List[IPairList] = [] self._tickers_needed = False - for pl in self._config.get('pairlists', None): if 'method' not in pl: logger.warning(f"No method in {pl}") continue pairl = PairListResolver(pl.get('method'), - exchange, self, config, - pl.get('config')).pairlist + exchange=exchange, + pairlistmanager=self, + config=config, + pairlistconfig=pl.get('config'), + pairlist_pos=len(self._pairlists) + ).pairlist self._tickers_needed = pairl.needstickers or self._tickers_needed self._pairlists.append(pairl) @@ -67,7 +70,7 @@ class PairListManager(): def refresh_pairlist(self) -> None: """ - Run pairlist through all pairlists. + Run pairlist through all configured pairlists. """ pairlist = self._whitelist.copy() diff --git a/freqtrade/resolvers/pairlist_resolver.py b/freqtrade/resolvers/pairlist_resolver.py index 9e051fa8f..d849f4ffb 100644 --- a/freqtrade/resolvers/pairlist_resolver.py +++ b/freqtrade/resolvers/pairlist_resolver.py @@ -21,7 +21,7 @@ class PairListResolver(IResolver): __slots__ = ['pairlist'] def __init__(self, pairlist_name: str, exchange, pairlistmanager, - config: dict, pairlistconfig) -> None: + config: dict, pairlistconfig: dict, pairlist_pos: int) -> None: """ Load the custom class from config parameter :param config: configuration dictionary or None @@ -30,7 +30,8 @@ class PairListResolver(IResolver): kwargs={'exchange': exchange, 'pairlistmanager': pairlistmanager, 'config': config, - 'pairlistconfig': pairlistconfig}) + 'pairlistconfig': pairlistconfig, + 'pairlist_pos': pairlist_pos}) def _load_pairlist( self, pairlist_name: str, config: dict, kwargs: dict) -> IPairList: diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 017f78561..c322dd8c4 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -55,7 +55,7 @@ def test_load_pairlist_noexist(mocker, markets, default_conf): with pytest.raises(OperationalException, match=r"Impossible to load Pairlist 'NonexistingPairList'. " r"This class does not exist or contains Python code errors."): - PairListResolver('NonexistingPairList', bot.exchange, plm, default_conf, {}) + PairListResolver('NonexistingPairList', bot.exchange, plm, default_conf, {}, 1) def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf): From 5caeca75096d2e2fd963bcb1645475c818864f96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 15:23:36 +0100 Subject: [PATCH 071/157] Improve tests for pairlist-sequence behaviour --- docs/developer.md | 9 ++--- tests/pairlist/test_pairlist.py | 63 +++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/docs/developer.md b/docs/developer.md index e63d970e2..c65f429e9 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -100,13 +100,14 @@ This is a simple provider, which however serves as a good example on how to star Next, modify the classname of the provider (ideally align this with the Filename). -The base-class provides an instance of the exchange (`self._exchange`) the pairlist manager (`self._pairlistmanager`), as well as the main configuration (`self._config`) and the pairlist dedicated configuration (`self._pairlistconfig`). +The base-class provides an instance of the exchange (`self._exchange`) the pairlist manager (`self._pairlistmanager`), as well as the main configuration (`self._config`), the pairlist dedicated configuration (`self._pairlistconfig`) and the absolute position within the list of pairlists. ```python - self._freqtrade = freqtrade + self._exchange = exchange + self._pairlistmanager = pairlistmanager self._config = config - self._whitelist = self._config['exchange']['pair_whitelist'] - self._blacklist = self._config['exchange'].get('pair_blacklist', []) + self._pairlistconfig = pairlistconfig + self._pairlist_pos = pairlist_pos ``` Now, let's step through the methods which require actions: diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index c322dd8c4..c40187cc9 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -135,25 +135,44 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): assert set(whitelist) == set(pairslist) -@pytest.mark.parametrize("filters,base_currency,key,whitelist_result", [ - ([], "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), - ([], "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'HOT/BTC', 'FUEL/BTC']), - ([], "USDT", "quoteVolume", ['ETH/USDT']), - ([], "ETH", "quoteVolume", []), - ([{"method": "PrecisionFilter"}], "BTC", - "quoteVolume", ["LTC/BTC", "ETH/BTC", "TKN/BTC", 'FUEL/BTC']), - ([{"method": "PrecisionFilter"}], - "BTC", "bidVolume", ["LTC/BTC", "TKN/BTC", "ETH/BTC", 'FUEL/BTC']), - ([{"method": "LowPriceFilter", "config": {"low_price_percent": 0.03}}], - "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'FUEL/BTC']), +@pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [ + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}], + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + # Different sorting depending on quote or bid volume + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "bidVolume"}}], + "BTC", ['HOT/BTC', 'FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}], + "USDT", ['ETH/USDT']), + # No pair for ETH ... + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}], + "ETH", []), + # Precisionfilter and quote volume + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}, + {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), + # Precisionfilter bid + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "bidVolume"}}, + {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + # Lowpricefilter and VolumePairList + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}, + {"method": "LowPriceFilter", "config": {"low_price_percent": 0.03}}], + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), # Hot is removed by precision_filter, Fuel by low_price_filter. - ([{"method": "PrecisionFilter"}, + ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}, + {"method": "PrecisionFilter"}, {"method": "LowPriceFilter", "config": {"low_price_percent": 0.02}} - ], "BTC", "bidVolume", ['LTC/BTC', 'TKN/BTC', 'ETH/BTC'])]) + ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), + # StaticPairlist Only + ([{"method": "StaticPairList"}, + ], "BTC", ['ETH/BTC', 'TKN/BTC']), + # Static Pairlist before VolumePairList - sorting changes + ([{"method": "StaticPairList"}, + {"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "bidVolume"}}, + ], "BTC", ['TKN/BTC', 'ETH/BTC']), +]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, - filters, base_currency, key, whitelist_result, + pairlists, base_currency, whitelist_result, caplog) -> None: - whitelist_conf['pairlists'].extend(filters) + whitelist_conf['pairlists'] = pairlists mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) @@ -167,13 +186,13 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t freqtrade.pairlists.refresh_pairlist() whitelist = freqtrade.pairlists.whitelist - assert sorted(whitelist) == sorted(whitelist_result) - if 'PrecisionFilter' in filters: - assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' - r'would be <= stop limit.*', caplog) - - if 'LowPriceFilter' in filters: - assert log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) + assert whitelist == whitelist_result + for pairlist in pairlists: + if pairlist['method'] == 'PrecisionFilter': + assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' + r'would be <= stop limit.*', caplog) + if pairlist['method'] == 'LowPriceFilter': + assert log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: From 0b4800835c9bfa7f08d2eac5fa228e35bdc06d9e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 15:26:30 +0100 Subject: [PATCH 072/157] update documentation --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8f62708d1..a8bfae6f9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -410,7 +410,7 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis `VolumePairList` selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume` and defaults to `quoteVolume`. -`VolumePairList` does not consider `pair_whitelist`, but selects the top assets from all available markets (with matching stake-currency) on the exchange. +`VolumePairList` considers outputs of previous pairlists unless it's the first configured pairlist, it does not consider `pair_whitelist`, but selects the top assets from all available markets (with matching stake-currency) on the exchange. `ttl` allows setting the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). From 86a5dfa62e3d72b4d37cf4589dcae68f88a5e1b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 15:28:36 +0100 Subject: [PATCH 073/157] Update documentation --- docs/configuration.md | 2 +- freqtrade/configuration/config_validation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a8bfae6f9..86acab8d6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -383,7 +383,7 @@ There are [`StaticPairList`](#static-pair-list) and dynamic Whitelists available [`PrecisionFilter`](#precision-filter) and [`LowPriceFilter`](#low-price-pair-filter) act as filters, removing low-value pairs. -All pairlists can be chained, and a combination of all pairlists will become your new whitelist. +All pairlists can be chained, and a combination of all pairlists will become your new whitelist. Pairlists are executed in the sequence they are configured. You should always configure either `StaticPairList` or `DynamicPairList` as starting pairlists. Inactive markets and blacklisted pairs are always removed from the resulting `pair_whitelist`. diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 21086c913..83d58c9e4 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -123,5 +123,5 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None: for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]): if (pl.get('method') == 'StaticPairList' - and not conf.get('exchange', {}).get('pair_whitelist')): + and not conf.get('exchange', {}).get('pair_whitelist')): raise OperationalException("StaticPairList requires pair_whitelist to be set.") From 4b15873ee151bc5a651bce0a0d39d3920f6aeb29 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 15:36:11 +0100 Subject: [PATCH 074/157] Simplify examples --- config_binance.json.example | 4 +--- config_kraken.json.example | 4 +--- tests/pairlist/test_pairlist.py | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/config_binance.json.example b/config_binance.json.example index aa36ed035..7d616fe91 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -55,9 +55,7 @@ ] }, "pairlists": [ - { - "method": "StaticPairList" - } + {"method": "StaticPairList"} ], "edge": { "enabled": false, diff --git a/config_kraken.json.example b/config_kraken.json.example index 9ca2ca065..854aeef71 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -47,9 +47,7 @@ ] }, "pairlists": [ - { - "method": "StaticPairList" - } + {"method": "StaticPairList"} ], "edge": { "enabled": false, diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index c40187cc9..0ec6766c2 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -291,4 +291,3 @@ def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog): with pytest.raises(OperationalException, match=r"No Pairlist defined!"): get_patched_freqtradebot(mocker, whitelist_conf) - From 12654cb810789031232fa58f25634cf000f4cb5b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 16:14:18 +0100 Subject: [PATCH 075/157] Add seperate exchange section in docs --- README.md | 8 +------- docs/configuration.md | 13 ++++--------- docs/data-download.md | 6 ++---- docs/exchanges.md | 35 +++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 5 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 docs/exchanges.md diff --git a/README.md b/README.md index 6d57dcd89..a1feeab67 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,6 @@ git checkout develop For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/). - ## Basic Usage ### Bot commands @@ -106,7 +105,7 @@ optional arguments: ### Telegram RPC commands -Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/) +Telegram is not mandatory. However, this is a great way to control your bot. More details and the full command list on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/) - `/start`: Starts the trader - `/stop`: Stops the trader @@ -129,11 +128,6 @@ The project is currently setup in two main branches: - `master` - This branch contains the latest stable release. The bot 'should' be stable on this branch, and is generally well tested. - `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature. -## A note on Binance - -For Binance, please add `"BNB/"` to your blacklist to avoid issues. -Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB order unsellable as the expected amount is not there anymore. - ## Support ### Help / Slack diff --git a/docs/configuration.md b/docs/configuration.md index e250a81ec..6439c9258 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -357,19 +357,12 @@ For example, to test the order type `FOK` with Kraken, and modify candle_limit t !!! Warning Please make sure to fully understand the impacts of these settings before modifying them. -#### Random notes for other exchanges - -* The Ocean (ccxt id: 'theocean') exchange uses Web3 functionality and requires web3 package to be installed: -```shell -$ pip3 install web3 -``` - ### What values can be used for fiat_display_currency? The `fiat_display_currency` configuration parameter sets the base currency to use for the conversion from coin to fiat in the bot Telegram reports. -The valid values are: +The valid values are:p ```json "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD" @@ -476,11 +469,13 @@ you run it in production mode. "secret": "08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5", ... } - ``` + !!! Note If you have an exchange API key yet, [see our tutorial](/pre-requisite). +You should also make sure to read the [Exchanges](exchanges.md) section of the documentation to be aware of potential configuration details specific to your exchange. + ### Using proxy with FreqTrade To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration. diff --git a/docs/data-download.md b/docs/data-download.md index f105e7a56..1f03b124a 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -78,10 +78,8 @@ freqtrade download-data --exchange binance --pairs XRP/ETH ETH/BTC --days 20 --d !!! Warning The historic trades are not available during Freqtrade dry-run and live trade modes because all exchanges tested provide this data with a delay of few 100 candles, so it's not suitable for real-time trading. -### Historic Kraken data - -The Kraken API does only provide 720 historic candles, which is sufficient for FreqTrade dry-run and live trade modes, but is a problem for backtesting. -To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data. +!!! Note "Kraken user" + Kraken users should read [this](exchanges.md#historic-kraken-data) before starting to download data. ## Next step diff --git a/docs/exchanges.md b/docs/exchanges.md new file mode 100644 index 000000000..8df76c1ba --- /dev/null +++ b/docs/exchanges.md @@ -0,0 +1,35 @@ +# Exchange-specific Notes + +This page combines common gotchas and informations which are exchange-specific and most likely don't apply to other exchanges. + +## Binance + +!!! Tip "Stoploss on Exchange" + Binance is currently the only exchange supporting `stoploss_on_exchange`. It provides great advantages, so we recommend to benefit from it. + +### Blacklists + +For Binance, please add `"BNB/"` to your blacklist to avoid issues. +Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB order unsellable as the expected amount is not there anymore. + +### Binance sites + +Binance has been split into 3, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized. + +* [binance.com](https://www.binance.com/) - International users - ccxt id: `binance` +* [binance.us](https://www.binance.us/) US based users- ccxt id: `binanceus` +* [binance.je](https://www.binance.je/) Trading FIAT currencies - ccxt id: `binanceje` + +### Kraken + +### Historic Kraken data + +The Kraken API does only provide 720 historic candles, which is sufficient for FreqTrade dry-run and live trade modes, but is a problem for backtesting. +To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data. + +#### Random notes for other exchanges + +* The Ocean (ccxt id: 'theocean') exchange uses Web3 functionality and requires web3 package to be installed: +```shell +$ pip3 install web3 +``` diff --git a/mkdocs.yml b/mkdocs.yml index 2c3f70191..0fd8070f5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,6 +16,7 @@ nav: - Hyperopt: hyperopt.md - Edge Positioning: edge.md - Utility Subcommands: utils.md + - Exchanges: exchanges.md - FAQ: faq.md - Data Analysis: - Jupyter Notebooks: data-analysis.md From de2d04f06b2221559a6703cfe3c77a3c74ab5257 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 16:24:24 +0100 Subject: [PATCH 076/157] Add note about systemd load location closes #2461 --- docs/advanced-setup.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index e6334d2c1..97d52850c 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -8,6 +8,9 @@ If you do not know what things mentioned here mean, you probably do not need it. Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup. +!!! Note + Certain systems (like Raspbian) don't load service unit files from the user directory. In this case, copy `freqtrade.service` into `/etc/systemd/user/` (requires superuser permissions). + After that you can start the daemon with: ```bash From 085aa3084ebc07a4bc5dac098577e38dbb415217 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2019 19:45:09 +0100 Subject: [PATCH 077/157] Implement ticker caching --- freqtrade/pairlist/pairlistmanager.py | 7 ++++++- requirements-common.txt | 1 + setup.py | 1 + tests/pairlist/test_pairlist.py | 4 +++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 309ada094..0734d7f8f 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -4,6 +4,7 @@ Static List provider Provides lists as configured in config.json """ +from cachetools import TTLCache, cached import logging from typing import Dict, List @@ -68,6 +69,10 @@ class PairListManager(): """ return [{p.name: p.short_desc()} for p in self._pairlists] + @cached(TTLCache(maxsize=1, ttl=1800)) + def _get_cached_tickers(self): + return self._exchange.get_tickers() + def refresh_pairlist(self) -> None: """ Run pairlist through all configured pairlists. @@ -78,7 +83,7 @@ class PairListManager(): # tickers should be cached to avoid calling the exchange on each call. tickers: Dict = {} if self._tickers_needed: - tickers = self._exchange.get_tickers() + tickers = self._get_cached_tickers() # Process all pairlists in chain for pl in self._pairlists: diff --git a/requirements-common.txt b/requirements-common.txt index 52b80d501..c11179fbb 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -4,6 +4,7 @@ ccxt==1.19.14 SQLAlchemy==1.3.10 python-telegram-bot==12.2.0 arrow==0.15.4 +cachetools==3.1.1 requests==2.22.0 urllib3==1.25.6 wrapt==1.11.2 diff --git a/setup.py b/setup.py index 781a5d138..50b8eee9c 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ setup(name='freqtrade', 'SQLAlchemy', 'python-telegram-bot', 'arrow', + 'cachetools', 'requests', 'urllib3', 'wrapt', diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 0ec6766c2..94b2147f5 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -268,12 +268,14 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): ) bot = get_patched_freqtradebot(mocker, whitelist_conf) assert bot.pairlists._pairlists[0]._last_refresh == 0 - + assert tickers.call_count == 0 bot.pairlists.refresh_pairlist() + assert tickers.call_count == 1 assert bot.pairlists._pairlists[0]._last_refresh != 0 lrf = bot.pairlists._pairlists[0]._last_refresh bot.pairlists.refresh_pairlist() + assert tickers.call_count == 1 # Time should not be updated. assert bot.pairlists._pairlists[0]._last_refresh == lrf From eba55c27832240febb5f9ec10651931b04cc686e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Nov 2019 19:31:13 +0100 Subject: [PATCH 078/157] Change link --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 0fd8070f5..43d6acc1d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,7 +16,7 @@ nav: - Hyperopt: hyperopt.md - Edge Positioning: edge.md - Utility Subcommands: utils.md - - Exchanges: exchanges.md + - Exchange-specific Notes: exchanges.md - FAQ: faq.md - Data Analysis: - Jupyter Notebooks: data-analysis.md From 692d6afbd9ec1953e713e2947270f4b2080ee755 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 11 Nov 2019 02:17:41 +0300 Subject: [PATCH 079/157] Minor exchange notes typographical cosmetics --- docs/exchanges.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 8df76c1ba..2cf4ed355 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -16,9 +16,9 @@ Accounts having BNB accounts use this to pay for fees - if your first trade happ Binance has been split into 3, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized. -* [binance.com](https://www.binance.com/) - International users - ccxt id: `binance` -* [binance.us](https://www.binance.us/) US based users- ccxt id: `binanceus` -* [binance.je](https://www.binance.je/) Trading FIAT currencies - ccxt id: `binanceje` +* [binance.com](https://www.binance.com/) - International users, ccxt id: `binance`. +* [binance.us](https://www.binance.us/) - US based users, ccxt id: `binanceus`. +* [binance.je](https://www.binance.je/) - Binance Jersey, trading fiat currencies. Use ccxt id: `binanceje`. ### Kraken From e810597eec91727b53ac3fe910887972a59850cd Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Nov 2019 07:16:35 +0100 Subject: [PATCH 080/157] Add restricted markets snippet to documentation --- docs/exchanges.md | 24 +++++++++++++++++++++++- docs/faq.md | 6 +----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 8df76c1ba..bb30e3f6d 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -20,7 +20,7 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f * [binance.us](https://www.binance.us/) US based users- ccxt id: `binanceus` * [binance.je](https://www.binance.je/) Trading FIAT currencies - ccxt id: `binanceje` -### Kraken +## Kraken ### Historic Kraken data @@ -33,3 +33,25 @@ To download data for the Kraken exchange, using `--dl-trades` is mandatory, othe ```shell $ pip3 install web3 ``` + +## Bittrex + +### Restricted markets + +Bittrex split its exchange into US and International versions. +The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. + +If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair. +If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you. +If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist. + +You can get a list of restricted markets by using the following snipptet: + +``` python +import ccxt +ct = ccxt.bittrex() +_ = ct.load_markets() +res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarkets()['result'] if x['IsRestricted']] +print(res) + +``` diff --git a/docs/faq.md b/docs/faq.md index 7fdd54958..3ff668bae 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -48,12 +48,8 @@ You can use the `/forcesell all` command from Telegram. ### I get the message "RESTRICTED_MARKET" Currently known to happen for US Bittrex users. -Bittrex split its exchange into US and International versions. -The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. -If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair. -If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you. -If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist. +Read [the Bittrex section about restricted markets](exchanges.md#Restricted markets) for more information. ### How do I search the bot logs for something? From 04b51a982e4aa6c5d88e699b4b0eab45af5183e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Nov 2019 08:55:37 +0100 Subject: [PATCH 081/157] Include warning-message to bittrex explanation --- docs/exchanges.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index bb30e3f6d..0006dfa34 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -42,6 +42,13 @@ Bittrex split its exchange into US and International versions. The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair. + +The warning message will look similar to the following: + +``` output +[...] Message: bittrex {"success":false,"message":"RESTRICTED_MARKET","result":null,"explanation":null}" +``` + If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you. If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist. From 83067c1edc4ac138c2d87b8b8cfc555f0ab384ff Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:18:43 +0300 Subject: [PATCH 082/157] minor: Fix link in the Faq docs --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 3ff668bae..b9c662085 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -49,7 +49,7 @@ You can use the `/forcesell all` command from Telegram. Currently known to happen for US Bittrex users. -Read [the Bittrex section about restricted markets](exchanges.md#Restricted markets) for more information. +Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information. ### How do I search the bot logs for something? From 661c8251c55ab01de6519be576214b867ca033a9 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:23:29 +0300 Subject: [PATCH 083/157] minor: Exchange notes docs * Formatting (structure of sections) * Cosmetic changes This was not noticed in terms of #2505 --- docs/exchanges.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 0006dfa34..ac742a350 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -27,13 +27,6 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f The Kraken API does only provide 720 historic candles, which is sufficient for FreqTrade dry-run and live trade modes, but is a problem for backtesting. To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data. -#### Random notes for other exchanges - -* The Ocean (ccxt id: 'theocean') exchange uses Web3 functionality and requires web3 package to be installed: -```shell -$ pip3 install web3 -``` - ## Bittrex ### Restricted markets @@ -41,7 +34,7 @@ $ pip3 install web3 Bittrex split its exchange into US and International versions. The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. -If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair. +If you have restricted pairs in your whitelist, you'll get a warning message in the log on Freqtrade startup for each restricted pair. The warning message will look similar to the following: @@ -49,8 +42,8 @@ The warning message will look similar to the following: [...] Message: bittrex {"success":false,"message":"RESTRICTED_MARKET","result":null,"explanation":null}" ``` -If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you. -If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist. +If you're an "International" customer on the Bittrex exchange, then this warning will probably not impact you. +If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your whitelist. You can get a list of restricted markets by using the following snipptet: @@ -62,3 +55,10 @@ res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarket print(res) ``` + +## Random notes for other exchanges + +* The Ocean (ccxt id: 'theocean') exchange uses Web3 functionality and requires web3 package to be installed: +```shell +$ pip3 install web3 +``` From 95492958f9fc74c113a02fbfbc51347f8e0d5e56 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:37:57 +0300 Subject: [PATCH 084/157] wordings --- docs/exchanges.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 2cf4ed355..6e4ddf9a8 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -16,9 +16,9 @@ Accounts having BNB accounts use this to pay for fees - if your first trade happ Binance has been split into 3, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized. -* [binance.com](https://www.binance.com/) - International users, ccxt id: `binance`. -* [binance.us](https://www.binance.us/) - US based users, ccxt id: `binanceus`. -* [binance.je](https://www.binance.je/) - Binance Jersey, trading fiat currencies. Use ccxt id: `binanceje`. +* [binance.com](https://www.binance.com/) - International users. Use exchange id: `binance`. +* [binance.us](https://www.binance.us/) - US based users. Use exchange id: `binanceus`. +* [binance.je](https://www.binance.je/) - Binance Jersey, trading fiat currencies. Use exchange id: `binanceje`. ### Kraken From 27d81bb68c6cabadfc929c49e3f1f5b1c537af57 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Mon, 11 Nov 2019 12:23:24 +0300 Subject: [PATCH 085/157] minor: More cosmetics on Exchange Notes --- docs/exchanges.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 090d70e1f..5bd283a69 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -24,7 +24,7 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f ### Historic Kraken data -The Kraken API does only provide 720 historic candles, which is sufficient for FreqTrade dry-run and live trade modes, but is a problem for backtesting. +The Kraken API does only provide 720 historic candles, which is sufficient for Freqtrade dry-run and live trade modes, but is a problem for backtesting. To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data. ## Bittrex @@ -45,7 +45,7 @@ The warning message will look similar to the following: If you're an "International" customer on the Bittrex exchange, then this warning will probably not impact you. If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your whitelist. -You can get a list of restricted markets by using the following snipptet: +You can get a list of restricted markets by using the following snippet: ``` python import ccxt @@ -53,12 +53,11 @@ ct = ccxt.bittrex() _ = ct.load_markets() res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarkets()['result'] if x['IsRestricted']] print(res) - ``` ## Random notes for other exchanges -* The Ocean (ccxt id: 'theocean') exchange uses Web3 functionality and requires web3 package to be installed: +* The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed: ```shell $ pip3 install web3 ``` From 0a13f7e1c780d1ebc643f235eb0f0f523d2eeb45 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 10:18:58 +0000 Subject: [PATCH 086/157] Bump ccxt from 1.19.14 to 1.19.25 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.19.14 to 1.19.25. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/1.19.14...1.19.25) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index c11179fbb..33a5d0776 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.19.14 +ccxt==1.19.25 SQLAlchemy==1.3.10 python-telegram-bot==12.2.0 arrow==0.15.4 From c65d217d1eae52a20a5a4834ec6031c621ebc256 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 10:19:36 +0000 Subject: [PATCH 087/157] Bump scipy from 1.3.1 to 1.3.2 Bumps [scipy](https://github.com/scipy/scipy) from 1.3.1 to 1.3.2. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.3.1...v1.3.2) Signed-off-by: dependabot-preview[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index f5dae7332..ff8de9cb2 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.3.1 +scipy==1.3.2 scikit-learn==0.21.3 scikit-optimize==0.5.2 filelock==3.0.12 From 031157f2153e581496c048e1151a2c3b4916a53a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 10:20:30 +0000 Subject: [PATCH 088/157] Bump numpy from 1.17.3 to 1.17.4 Bumps [numpy](https://github.com/numpy/numpy) from 1.17.3 to 1.17.4. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/master/doc/HOWTO_RELEASE.rst.txt) - [Commits](https://github.com/numpy/numpy/compare/v1.17.3...v1.17.4) Signed-off-by: dependabot-preview[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 331e3dc67..ebf27abd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # Load common requirements -r requirements-common.txt -numpy==1.17.3 +numpy==1.17.4 pandas==0.25.3 From ff1d36434d7f66e53f81c01bbb5fa5be359f0761 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Nov 2019 15:33:48 +0100 Subject: [PATCH 089/157] Add github actions action --- .github/workflows/ci.yml | 167 ++++++++++++++++++++++++++++++ build_helpers/install_windows.ps1 | 6 ++ build_helpers/publish_docker.sh | 20 ++-- tests/test_docs.sh | 12 +++ 4 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 build_helpers/install_windows.ps1 create mode 100755 tests/test_docs.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..9af0e0c64 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,167 @@ +name: Freqtrade CI + +on: + push: + branches: + - master + - develop + tags: + pull_request: + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-18.04] + python-version: [3.7] + + steps: + - uses: actions/checkout@v1 + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache_dependencies + uses: actions/cache@v1 + id: cache + with: + path: ~/dependencies/ + key: ${{ runner.os }}-dependencies + + - name: pip cache (linux) + uses: actions/cache@preview + if: startsWith(matrix.os, 'ubuntu') + with: + path: ~/.cache/pip + key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip + + - name: pip cache (macOS) + uses: actions/cache@preview + if: startsWith(matrix.os, 'macOS') + with: + path: ~/Library/Caches/pip + key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip + + - name: TA binary *nix + if: steps.cache.outputs.cache-hit != 'true' + run: | + cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd .. + + - name: Installation - *nix + run: | + python -m pip install --upgrade pip + export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH + export TA_LIBRARY_PATH=${HOME}/dependencies/lib + export TA_INCLUDE_PATH=${HOME}/dependencies/include + pip install -r requirements-dev.txt + pip install -e . + + - name: Tests + run: | + pytest --random-order --cov=freqtrade --cov-config=.coveragerc + # Allow failure for coveralls + coveralls || true + + - name: Backtesting + run: | + cp config.json.example config.json + freqtrade --datadir tests/testdata backtesting + + - name: Hyperopt + run: | + cp config.json.example config.json + freqtrade --datadir tests/testdata --strategy SampleStrategy hyperopt --customhyperopt SampleHyperOpts -e 5 + + - name: Flake8 + run: | + flake8 + + - name: Mypy + run: | + mypy freqtrade scripts + + - name: Documentation syntax + run: | + ./tests/test_docs.sh + + build_windows: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ windows-latest ] + python-version: [3.7] + + steps: + - uses: actions/checkout@v1 + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Pip cache (Windows) + uses: actions/cache@preview + if: startsWith(runner.os, 'Windows') + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip + restore-keys: ${{ runner.os }}-pip + + - name: Installation + run: | + ./build_helpers/install_windows.ps1 + + - name: Tests + run: | + pytest --random-order --cov=freqtrade --cov-config=.coveragerc + + - name: Backtesting + run: | + cp config.json.example config.json + freqtrade --datadir tests/testdata backtesting + + - name: Hyperopt + run: | + cp config.json.example config.json + freqtrade --datadir tests/testdata --strategy SampleStrategy hyperopt --customhyperopt SampleHyperOpts -e 5 + + - name: Flake8 + run: | + flake8 + + - name: Mypy + run: | + mypy freqtrade scripts + + deploy: + needs: [ build, build_windows ] + runs-on: ubuntu-18.04 + if: github.event_name == 'push' || 1 == 1 + steps: + - uses: actions/checkout@v1 + + - name: Build and test and push docker image + env: + IMAGE_NAME: freqtradeorg/freqtradetests + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + # original filter + # branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) + run: | + build_helpers/publish_docker.sh + + - name: Build raspberry image + uses: elgohr/Publish-Docker-Github-Action@2.7 + with: + name: freqtradeorg/freqtradetests:test_pi + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + dockerfile: Dockerfile.pi + cache: ${{ github.event_name != 'cron' }} + tag_names: true + diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 new file mode 100644 index 000000000..e897cb88c --- /dev/null +++ b/build_helpers/install_windows.ps1 @@ -0,0 +1,6 @@ +Invoke-WebRequest -Uri "https://download.lfd.uci.edu/pythonlibs/g5apjq5m/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" -OutFile "TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" + +pip install TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl + +pip install -r requirements-dev.txt +pip install -e . diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh index 839ca0876..cac2a4c04 100755 --- a/build_helpers/publish_docker.sh +++ b/build_helpers/publish_docker.sh @@ -1,17 +1,17 @@ #!/bin/sh -# - export TAG=`if [ "$TRAVIS_BRANCH" == "develop" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` -# Replace / with _ to create a valid tag -TAG=$(echo "${TRAVIS_BRANCH}" | sed -e "s/\//_/") +# Replace / with _ to create a valid tag +TAG=$(echo "${GITHUB_REF}" | sed -e "s/\//_/g") +echo "Running for ${TAG}" # Add commit and commit_message to docker container -echo "${TRAVIS_COMMIT} ${TRAVIS_COMMIT_MESSAGE}" > freqtrade_commit +echo "${GITHUB_SHA}" > freqtrade_commit -if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then - echo "event ${TRAVIS_EVENT_TYPE}: full rebuild - skipping cache" +if [ "${GITHUB_EVENT_NAME}" = "cron" ]; then + echo "event ${GITHUB_EVENT_NAME}: full rebuild - skipping cache" docker build -t freqtrade:${TAG} . else - echo "event ${TRAVIS_EVENT_TYPE}: building with cache" + echo "event ${GITHUB_EVENT_NAME}: building with cache" # Pull last build to avoid rebuilding the whole image docker pull ${IMAGE_NAME}:${TAG} docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} . @@ -23,7 +23,7 @@ if [ $? -ne 0 ]; then fi # Run backtest -docker run --rm -it -v $(pwd)/config.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} --datadir /tests/testdata backtesting +docker run --rm -v $(pwd)/config.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} --datadir /tests/testdata backtesting if [ $? -ne 0 ]; then echo "failed running backtest" @@ -38,12 +38,12 @@ if [ $? -ne 0 ]; then fi # Tag as latest for develop builds -if [ "${TRAVIS_BRANCH}" = "develop" ]; then +if [ "${GITHUB_REF}" = "develop" ]; then docker tag freqtrade:$TAG ${IMAGE_NAME}:latest fi # Login -echo "$DOCKER_PASS" | docker login -u $DOCKER_USER --password-stdin +docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD if [ $? -ne 0 ]; then echo "failed login" diff --git a/tests/test_docs.sh b/tests/test_docs.sh new file mode 100755 index 000000000..09e142b99 --- /dev/null +++ b/tests/test_docs.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Test Documentation boxes - +# !!! : is not allowed! +# !!! "title" - Title needs to be quoted! +grep -Er '^!{3}\s\S+:|^!{3}\s\S+\s[^"]' docs/* + +if [ $? -ne 0 ]; then + echo "Docs test success." + exit 0 +fi +echo "Docs test failed." +exit 1 From e51a7201934db8873244110b1d5d408169ac5d10 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Nov 2019 10:02:26 +0100 Subject: [PATCH 090/157] Apply cache to pi image --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9af0e0c64..aa2d09750 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,6 +162,7 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} dockerfile: Dockerfile.pi + # cache: true cache: ${{ github.event_name != 'cron' }} tag_names: true From d1729a624d99b8d509813cea2d2421eff11457ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Nov 2019 10:36:38 +0100 Subject: [PATCH 091/157] fix windows build --- .github/workflows/ci.yml | 15 +++++++++++---- .../TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl | Bin 0 -> 684920 bytes build_helpers/install_windows.ps1 | 6 ++++-- build_helpers/publish_docker.sh | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 build_helpers/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa2d09750..4edd357bf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: branches: - master - develop + - github_actions_2 tags: pull_request: @@ -14,7 +15,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-18.04] + os: [ ubuntu-18.04 ] python-version: [3.7] steps: @@ -141,24 +142,30 @@ jobs: deploy: needs: [ build, build_windows ] runs-on: ubuntu-18.04 - if: github.event_name == 'push' || 1 == 1 + if: github.event_name == 'push' || github.event_name == 'cron' steps: - uses: actions/checkout@v1 + - name: Extract branch name + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + id: extract_branch + - name: Build and test and push docker image env: IMAGE_NAME: freqtradeorg/freqtradetests DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} # original filter # branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) run: | build_helpers/publish_docker.sh - - name: Build raspberry image + - name: Build raspberry image for ${{ steps.extract_branch.outputs.branch }}_pi uses: elgohr/Publish-Docker-Github-Action@2.7 with: - name: freqtradeorg/freqtradetests:test_pi + name: freqtradeorg/freqtradetests:${{ steps.extract_branch.outputs.branch }}_pi username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} dockerfile: Dockerfile.pi diff --git a/build_helpers/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl b/build_helpers/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl new file mode 100644 index 0000000000000000000000000000000000000000..87469a199037a6ce79f4ee4ec347f0c411b8bb49 GIT binary patch literal 684920 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≪c7F=`hCoL#u}a7*VA+qt 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

    e}jSYxSKLA#+4<_BX0`|L| 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

    I_c5
    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

    SDtZ;i8C_#Z`^9CH+x{5WRg57UIK} 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 z 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 literal 0 HcmV?d00001 diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 index e897cb88c..30427c3cc 100644 --- a/build_helpers/install_windows.ps1 +++ b/build_helpers/install_windows.ps1 @@ -1,6 +1,8 @@ -Invoke-WebRequest -Uri "https://download.lfd.uci.edu/pythonlibs/g5apjq5m/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" -OutFile "TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" +# Downloads don't work automatically, since the URL is regenerated via javascript. +# Downloaded from https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib +# Invoke-WebRequest -Uri "https://download.lfd.uci.edu/pythonlibs/xxxxxxx/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" -OutFile "TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" -pip install TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl +pip install build_helpers\TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl pip install -r requirements-dev.txt pip install -e . diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh index cac2a4c04..e87047fb0 100755 --- a/build_helpers/publish_docker.sh +++ b/build_helpers/publish_docker.sh @@ -1,7 +1,7 @@ #!/bin/sh # Replace / with _ to create a valid tag -TAG=$(echo "${GITHUB_REF}" | sed -e "s/\//_/g") +TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g") echo "Running for ${TAG}" # Add commit and commit_message to docker container From a241c2af0d4a8f73084896217f3e69a247429cae Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Nov 2019 19:42:05 +0100 Subject: [PATCH 092/157] Build macos - ... --- .github/workflows/ci.yml | 10 +++++----- build_helpers/publish_docker.sh | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4edd357bf..28ff5fa8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,8 @@ on: - github_actions_2 tags: pull_request: + schedule: + - cron: '0 5 * * 4' jobs: build: @@ -15,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-18.04 ] + os: [ ubuntu-18.04, macos-latest ] python-version: [3.7] steps: @@ -142,7 +144,7 @@ jobs: deploy: needs: [ build, build_windows ] runs-on: ubuntu-18.04 - if: github.event_name == 'push' || github.event_name == 'cron' + if: github.event_name == 'push' || github.event_name == 'schedule' steps: - uses: actions/checkout@v1 @@ -157,8 +159,6 @@ jobs: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} - # original filter - # branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) run: | build_helpers/publish_docker.sh @@ -170,6 +170,6 @@ jobs: password: ${{ secrets.DOCKER_PASSWORD }} dockerfile: Dockerfile.pi # cache: true - cache: ${{ github.event_name != 'cron' }} + cache: ${{ github.event_name != 'schedule' }} tag_names: true diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh index e87047fb0..c536e8798 100755 --- a/build_helpers/publish_docker.sh +++ b/build_helpers/publish_docker.sh @@ -7,7 +7,7 @@ echo "Running for ${TAG}" # Add commit and commit_message to docker container echo "${GITHUB_SHA}" > freqtrade_commit -if [ "${GITHUB_EVENT_NAME}" = "cron" ]; then +if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then echo "event ${GITHUB_EVENT_NAME}: full rebuild - skipping cache" docker build -t freqtrade:${TAG} . else From 75d5ff69ef78c30f63c061d0012f1ae9c822703c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Nov 2019 20:09:58 +0100 Subject: [PATCH 093/157] Add ping endpoing --- docs/rest-api.md | 9 ++++++++- freqtrade/rpc/api_server.py | 9 +++++++++ tests/rpc/test_rpc_apiserver.py | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/rest-api.md b/docs/rest-api.md index 4d5bc5730..85435e0db 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -22,7 +22,14 @@ Sample configuration: !!! Danger "Password selection" Please make sure to select a very strong, unique password to protect your bot from unauthorized access. -You can then access the API by going to `http://127.0.0.1:8080/api/v1/version` to check if the API is running correctly. +You can then access the API by going to `http://127.0.0.1:8080/api/v1/ping` to check if the API is running correctly. +This should return the response: + +``` output +{"status":"pong"} +``` + +All other endpoints will require authentication, so are not available through a webrowser. To generate a secure password, either use a password manager, or use the below code snipped. diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 67bbfdc78..5167823fd 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -169,6 +169,8 @@ class ApiServer(RPC): view_func=self._status, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/version', 'version', view_func=self._version, methods=['GET']) + self.app.add_url_rule(f'{BASE_URI}/ping', 'ping', + view_func=self._ping, methods=['GET']) # Combined actions and infos self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist, @@ -224,6 +226,13 @@ class ApiServer(RPC): msg = self._rpc_stopbuy() return self.rest_dump(msg) + @rpc_catch_errors + def _ping(self): + """ + simple poing version + """ + return self.rest_dump({"status": "pong"}) + @require_login @rpc_catch_errors def _version(self): diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index b572a0514..6e65cf934 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -64,6 +64,10 @@ def test_api_not_found(botclient): def test_api_unauthorized(botclient): ftbot, client = botclient + rc = client.get(f"{BASE_URI}/ping") + assert_response(rc) + assert rc.json == {'status': 'pong'} + # Don't send user/pass information rc = client.get(f"{BASE_URI}/version") assert_response(rc, 401) From 800997437a521cf7ab2923f03b354ce0ca5c5152 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Nov 2019 20:25:44 +0100 Subject: [PATCH 094/157] Update documentation --- docs/rest-api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rest-api.md b/docs/rest-api.md index 85435e0db..00c2d8e39 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -22,14 +22,14 @@ Sample configuration: !!! Danger "Password selection" Please make sure to select a very strong, unique password to protect your bot from unauthorized access. -You can then access the API by going to `http://127.0.0.1:8080/api/v1/ping` to check if the API is running correctly. +You can then access the API by going to `http://127.0.0.1:8080/api/v1/ping` in a browser to check if the API is running correctly. This should return the response: ``` output {"status":"pong"} ``` -All other endpoints will require authentication, so are not available through a webrowser. +All other endpoints return sensitive info and require authentication, so are not available through a webrowser. To generate a secure password, either use a password manager, or use the below code snipped. From 025350ebff08351f29c1171b4057ed4a391658f4 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Tue, 12 Nov 2019 00:07:27 +0300 Subject: [PATCH 095/157] Fix typo in the rest-api docs --- docs/rest-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rest-api.md b/docs/rest-api.md index 00c2d8e39..d0e4c8247 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -29,7 +29,7 @@ This should return the response: {"status":"pong"} ``` -All other endpoints return sensitive info and require authentication, so are not available through a webrowser. +All other endpoints return sensitive info and require authentication, so are not available through a web browser. To generate a secure password, either use a password manager, or use the below code snipped. From 52e24c3a257c5ddd53c255516d578205a5b1277a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 09:27:53 +0100 Subject: [PATCH 096/157] Split error-messsage between incompatible and wrong stake amount --- freqtrade/pairlist/IPairList.py | 7 ++++++- tests/pairlist/test_pairlist.py | 14 ++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index 231755cb0..d722e70f5 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -94,10 +94,15 @@ class IPairList(ABC): sanitized_whitelist: List[str] = [] for pair in pairlist: # pair is not in the generated dynamic market or has the wrong stake currency - if (pair not in markets or not pair.endswith(self._config['stake_currency'])): + if pair not in markets: logger.warning(f"Pair {pair} is not compatible with exchange " f"{self._exchange.name}. Removing it from whitelist..") continue + if not pair.endswith(self._config['stake_currency']): + logger.warning(f"Pair {pair} is not compatible with your stake currency " + f"{self._config['stake_currency']}. Removing it from whitelist..") + continue + # Check if market is active market = markets[pair] if not market_is_active(market): diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 94b2147f5..13f868c7a 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -227,10 +227,16 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) @pytest.mark.parametrize("whitelist,log_message", [ (['ETH/BTC', 'TKN/BTC'], ""), - (['ETH/BTC', 'TKN/BTC', 'TRX/ETH'], "is not compatible with exchange"), # TRX/ETH wrong stake - (['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), # BCH/BTC not available - (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "in your blacklist. Removing "), # BLK/BTC in blacklist - (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active") # BTT/BTC is inactive + # TRX/ETH not in markets + (['ETH/BTC', 'TKN/BTC', 'TRX/ETH'], "is not compatible with exchange"), + # wrong stake + (['ETH/BTC', 'TKN/BTC', 'ETH/USDT'], "is not compatible with your stake currency"), + # BCH/BTC not available + (['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), + # BLK/BTC in blacklist + (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "in your blacklist. Removing "), + # BTT/BTC is inactive + (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active") ]) def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog, log_message, tickers): From 37ef5c38f0309bca902f0a18e312395b94bc2edd Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 09:39:30 +0100 Subject: [PATCH 097/157] integrate Slack notification --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++++++++++ .travis.yml | 10 +++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28ff5fa8a..015585af1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,6 +91,17 @@ jobs: run: | ./tests/test_docs.sh + - name: Slack Notification + uses: homoluctus/slatify@v1.8.0 + if: always() + with: + type: ${{ job.status }} + job_name: '*Freqtrade CI ${{ matrix.os }}*' + mention: 'here' + mention_if: 'failure' + channel: '#notifications' + url: ${{ secrets.SLACK_WEBHOOK }} + build_windows: runs-on: ${{ matrix.os }} @@ -141,6 +152,17 @@ jobs: run: | mypy freqtrade scripts + - name: Slack Notification + uses: homoluctus/slatify@v1.8.0 + if: always() + with: + type: ${{ job.status }} + job_name: '*Freqtrade CI windows*' + mention: 'here' + mention_if: 'failure' + channel: '#notifications' + url: ${{ secrets.SLACK_WEBHOOK }} + deploy: needs: [ build, build_windows ] runs-on: ubuntu-18.04 @@ -173,3 +195,14 @@ jobs: cache: ${{ github.event_name != 'schedule' }} tag_names: true + - name: Slack Notification + uses: homoluctus/slatify@v1.8.0 + if: always() + with: + type: ${{ job.status }} + job_name: '*Freqtrade CI Deploy*' + mention: 'here' + mention_if: 'failure' + channel: '#notifications' + url: ${{ secrets.SLACK_WEBHOOK }} + diff --git a/.travis.yml b/.travis.yml index 1cc22dfbd..ca35ef4ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,11 +45,11 @@ jobs: - script: mypy freqtrade scripts name: mypy - - stage: docker - if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) - script: - - build_helpers/publish_docker.sh - name: "Build and test and push docker image" + # - stage: docker + # if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) + # script: + # - build_helpers/publish_docker.sh + # name: "Build and test and push docker image" notifications: slack: From 96f550c6aab6081666eacda17c100d114c11bdad Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 10:35:36 +0100 Subject: [PATCH 098/157] Disable tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 015585af1..f7da23156 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: - master - develop - - github_actions_2 + - github_actions_tests tags: pull_request: schedule: From 8c76f45030d74ff856f089561a121ec91d03ad46 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 10:53:48 +0100 Subject: [PATCH 099/157] Use correct dockerhub image name --- .github/workflows/ci.yml | 4 ++-- docs/developer.md | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7da23156..67862282a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -177,7 +177,7 @@ jobs: - name: Build and test and push docker image env: - IMAGE_NAME: freqtradeorg/freqtradetests + IMAGE_NAME: freqtradeorg/freqtrade DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} @@ -187,7 +187,7 @@ jobs: - name: Build raspberry image for ${{ steps.extract_branch.outputs.branch }}_pi uses: elgohr/Publish-Docker-Github-Action@2.7 with: - name: freqtradeorg/freqtradetests:${{ steps.extract_branch.outputs.branch }}_pi + name: freqtradeorg/freqtrade:${{ steps.extract_branch.outputs.branch }}_pi username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} dockerfile: Dockerfile.pi diff --git a/docs/developer.md b/docs/developer.md index 67c0912a7..41c66ce37 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -198,6 +198,19 @@ jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace user_data/not jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown user_data/notebooks/strategy_analysis_example.ipynb --stdout > docs/strategy_analysis_example.md ``` +## Continuous integration + +This documents some decisions taken for the CI Pipeline. + +* CI runs on all OS variants, Linux (ubuntu), macOS and Windows. +* Docker images are build for the branches `master` and `develop`. +* Raspberry PI Docker images are postfixed with `_pi` - so tags will be `:master_pi` and `develop_pi`. +* Docker images contain a file, `/freqtrade/freqtrade_commit` containing the commit this image is based of. +* Full docker image rebuilds are run once a week via schedule. +* Deployments run on ubuntu. +* ta-lib binaries are contained in the build_helpers directory to avoid fails related to external unavailability. +* All tests must pass for a PR to be merged to `master` or `develop`. + ## Creating a release This part of the documentation is aimed at maintainers, and shows how to create a release. From e8a8f416f3b1a076bc668b724339143fcb373521 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Aug 2019 20:08:49 +0200 Subject: [PATCH 100/157] Update dockerhub description from github readme.md --- .github/workflows/docker_update_readme.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/docker_update_readme.yml diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml new file mode 100644 index 000000000..bc063617a --- /dev/null +++ b/.github/workflows/docker_update_readme.yml @@ -0,0 +1,18 @@ +name: Update Docker Hub Description +on: + push: + branches: + - master + +jobs: + dockerHubDescription: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Docker Hub Description + uses: peter-evans/dockerhub-description@v2.0.0 + env: + DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + DOCKERHUB_REPOSITORY: freqtradeorg/freqtrade + From 136ef077b2c96179981c1980dc7827a555cca981 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 13:14:43 +0100 Subject: [PATCH 101/157] Add sleep to allow thread to start --- tests/rpc/test_rpc_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py index 7278f0671..c9fbf8c3b 100644 --- a/tests/rpc/test_rpc_manager.py +++ b/tests/rpc/test_rpc_manager.py @@ -1,5 +1,5 @@ # pragma pylint: disable=missing-docstring, C0103 - +import time import logging from unittest.mock import MagicMock @@ -176,6 +176,8 @@ def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None: "listen_port": "8080"} rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) + # Sleep to allow the thread to start + time.sleep(0.5) assert log_has('Enabling rpc.api_server', caplog) assert len(rpc_manager.registered_modules) == 1 assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules] From ab9506df480e53316b65cd43d72c757e3dc18ab4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 13:54:26 +0100 Subject: [PATCH 102/157] simplify status_table command --- freqtrade/rpc/api_server.py | 2 +- freqtrade/rpc/rpc.py | 16 +++++++--------- freqtrade/rpc/telegram.py | 4 ++-- tests/rpc/test_rpc.py | 12 +++++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 5167823fd..3b59c9592 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -274,7 +274,7 @@ class ApiServer(RPC): stats = self._rpc_daily_profit(timescale, self._config['stake_currency'], - self._config['fiat_display_currency'] + self._config.get('fiat_display_currency', '') ) return self.rest_dump(stats) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 4aed48f74..84e681992 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -3,16 +3,15 @@ This module contains class to define a RPC communications """ import logging from abc import abstractmethod -from datetime import timedelta, datetime, date +from datetime import date, datetime, timedelta from decimal import Decimal from enum import Enum -from typing import Dict, Any, List, Optional +from typing import Any, Dict, List, Optional, Tuple import arrow -from numpy import mean, NAN -from pandas import DataFrame +from numpy import NAN, mean -from freqtrade import TemporaryError, DependencyException +from freqtrade import DependencyException, TemporaryError from freqtrade.misc import shorten_date from freqtrade.persistence import Trade from freqtrade.rpc.fiat_convert import CryptoToFiatConverter @@ -117,7 +116,7 @@ class RPC: results.append(trade_dict) return results - def _rpc_status_table(self) -> DataFrame: + def _rpc_status_table(self, fiat_display_currency: str) -> Tuple[List, List]: trades = Trade.get_open_trades() if not trades: raise RPCException('no active order') @@ -135,12 +134,11 @@ class RPC: trade.pair, shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), f'{trade_perc:.2f}%' + ]) columns = ['ID', 'Pair', 'Since', 'Profit'] - df_statuses = DataFrame.from_records(trades_list, columns=columns) - df_statuses = df_statuses.set_index(columns[0]) - return df_statuses + return trades_list, columns def _rpc_daily_profit( self, timescale: int, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 80582a0ce..25422973d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -234,8 +234,8 @@ class Telegram(RPC): :return: None """ try: - df_statuses = self._rpc_status_table() - message = tabulate(df_statuses, headers='keys', tablefmt='simple') + statlist, head = self._rpc_status_table(self._config.get('fiat_display_currency', '')) + message = tabulate(statlist, headers=head, tablefmt='simple') self._send_msg(f"

    {message}
    ", parse_mode=ParseMode.HTML) except RPCException as e: self._send_msg(str(e)) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index df2261c1f..06203a3d7 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -109,13 +109,15 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active order*'): - rpc._rpc_status_table() + rpc._rpc_status_table('USD') freqtradebot.create_trades() - result = rpc._rpc_status_table() - assert 'instantly' in result['Since'].all() - assert 'ETH/BTC' in result['Pair'].all() - assert '-0.59%' in result['Profit'].all() + result, headers = rpc._rpc_status_table('USD') + assert "Since" in headers + assert "Pair" in headers + assert 'instantly' in result[0][2] + assert 'ETH/BTC' in result[0][1] + assert '-0.59%' in result[0][3] mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) From df9bfb6c2e02b476955d137d45c637105ae2ed16 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 14:58:41 +0100 Subject: [PATCH 103/157] Add FIAT currency to status-table --- freqtrade/rpc/rpc.py | 21 +++++++++++++++++---- freqtrade/rpc/telegram.py | 3 ++- tests/rpc/test_rpc.py | 33 ++++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 84e681992..f15bf3cd0 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -6,6 +6,7 @@ from abc import abstractmethod from datetime import date, datetime, timedelta from decimal import Decimal from enum import Enum +from math import isnan from typing import Any, Dict, List, Optional, Tuple import arrow @@ -116,7 +117,7 @@ class RPC: results.append(trade_dict) return results - def _rpc_status_table(self, fiat_display_currency: str) -> Tuple[List, List]: + def _rpc_status_table(self, stake_currency, fiat_display_currency: str) -> Tuple[List, List]: trades = Trade.get_open_trades() if not trades: raise RPCException('no active order') @@ -129,15 +130,27 @@ class RPC: except DependencyException: current_rate = NAN trade_perc = (100 * trade.calc_profit_percent(current_rate)) + trade_profit = trade.calc_profit(current_rate) + profit_str = f'{trade_perc:.2f}%' + if self._fiat_converter: + fiat_profit = self._fiat_converter.convert_amount( + trade_profit, + stake_currency, + fiat_display_currency + ) + if fiat_profit and not isnan(fiat_profit): + profit_str += f" ({fiat_profit:.2f})" trades_list.append([ trade.id, trade.pair, shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), - f'{trade_perc:.2f}%' - + profit_str ]) + profitcol = "Profit" + if self._fiat_converter: + profitcol += " (" + fiat_display_currency + ")" - columns = ['ID', 'Pair', 'Since', 'Profit'] + columns = ['ID', 'Pair', 'Since', profitcol] return trades_list, columns def _rpc_daily_profit( diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 25422973d..8a81848ac 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -234,7 +234,8 @@ class Telegram(RPC): :return: None """ try: - statlist, head = self._rpc_status_table(self._config.get('fiat_display_currency', '')) + statlist, head = self._rpc_status_table(self._config['stake_currency'], + self._config.get('fiat_display_currency', '')) message = tabulate(statlist, headers=head, tablefmt='simple') self._send_msg(f"
    {message}
    ", parse_mode=ParseMode.HTML) except RPCException as e: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 06203a3d7..e7f41f6e7 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -96,6 +96,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: + mocker.patch.multiple( + 'freqtrade.rpc.fiat_convert.Market', + ticker=MagicMock(return_value={'price_usd': 15000.0}), + ) + mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -109,24 +114,34 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: freqtradebot.state = State.RUNNING with pytest.raises(RPCException, match=r'.*no active order*'): - rpc._rpc_status_table('USD') + rpc._rpc_status_table(default_conf['stake_currency'], 'USD') freqtradebot.create_trades() - result, headers = rpc._rpc_status_table('USD') + + result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert "Since" in headers assert "Pair" in headers - assert 'instantly' in result[0][2] - assert 'ETH/BTC' in result[0][1] - assert '-0.59%' in result[0][3] + assert 'instantly' == result[0][2] + assert 'ETH/BTC' == result[0][1] + assert '-0.59%' == result[0][3] + # Test with fiatconvert + + rpc._fiat_converter = CryptoToFiatConverter() + result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') + assert "Since" in headers + assert "Pair" in headers + assert 'instantly' == result[0][2] + assert 'ETH/BTC' == result[0][1] + assert '-0.59% (-0.09)' == result[0][3] mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available"))) # invalidate ticker cache rpc._freqtrade.exchange._cached_ticker = {} - result = rpc._rpc_status_table() - assert 'instantly' in result['Since'].all() - assert 'ETH/BTC' in result['Pair'].all() - assert 'nan%' in result['Profit'].all() + result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') + assert 'instantly' == result[0][2] + assert 'ETH/BTC' == result[0][1] + assert 'nan%' == result[0][3] def test_rpc_daily_profit(default_conf, update, ticker, fee, From 11f7ab61b9cf7958f44f2480c11cc787c1c21fb8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 15:11:31 +0100 Subject: [PATCH 104/157] Remove decimal import from rpc --- freqtrade/rpc/rpc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f15bf3cd0..5ab92cf33 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -4,7 +4,6 @@ This module contains class to define a RPC communications import logging from abc import abstractmethod from datetime import date, datetime, timedelta -from decimal import Decimal from enum import Enum from math import isnan from typing import Any, Dict, List, Optional, Tuple @@ -230,7 +229,7 @@ class RPC: profit_percent = trade.calc_profit_percent(rate=current_rate) profit_all_coin.append( - trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)) + trade.calc_profit(rate=trade.close_rate or current_rate) ) profit_all_perc.append(profit_percent) From e4bdb92521d38c062a0b266345727cfbe893899a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Nov 2019 20:19:13 +0100 Subject: [PATCH 105/157] Replace some occurances of ticker_interval with timeframe --- freqtrade/data/converter.py | 10 ++--- freqtrade/data/dataprovider.py | 16 +++---- freqtrade/data/history.py | 72 +++++++++++++++--------------- freqtrade/edge/__init__.py | 2 +- freqtrade/optimize/backtesting.py | 2 +- freqtrade/plot/plotting.py | 13 +++--- tests/data/test_btanalysis.py | 8 ++-- tests/data/test_converter.py | 2 +- tests/data/test_dataprovider.py | 2 +- tests/data/test_history.py | 58 ++++++++++++------------ tests/edge/test_edge.py | 2 +- tests/optimize/test_backtesting.py | 16 +++---- tests/test_plotting.py | 10 ++--- 13 files changed, 107 insertions(+), 106 deletions(-) diff --git a/freqtrade/data/converter.py b/freqtrade/data/converter.py index 1ef224978..e45dd451e 100644 --- a/freqtrade/data/converter.py +++ b/freqtrade/data/converter.py @@ -10,13 +10,13 @@ from pandas import DataFrame, to_datetime logger = logging.getLogger(__name__) -def parse_ticker_dataframe(ticker: list, ticker_interval: str, pair: str, *, +def parse_ticker_dataframe(ticker: list, timeframe: str, pair: str, *, fill_missing: bool = True, drop_incomplete: bool = True) -> DataFrame: """ Converts a ticker-list (format ccxt.fetch_ohlcv) to a Dataframe :param ticker: ticker list, as returned by exchange.async_get_candle_history - :param ticker_interval: ticker_interval (e.g. 5m). Used to fill up eventual missing data + :param timeframe: timeframe (e.g. 5m). Used to fill up eventual missing data :param pair: Pair this data is for (used to warn if fillup was necessary) :param fill_missing: fill up missing candles with 0 candles (see ohlcv_fill_up_missing_data for details) @@ -52,12 +52,12 @@ def parse_ticker_dataframe(ticker: list, ticker_interval: str, pair: str, *, logger.debug('Dropping last candle') if fill_missing: - return ohlcv_fill_up_missing_data(frame, ticker_interval, pair) + return ohlcv_fill_up_missing_data(frame, timeframe, pair) else: return frame -def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str, pair: str) -> DataFrame: +def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str) -> DataFrame: """ Fills up missing data with 0 volume rows, using the previous close as price for "open", "high" "low" and "close", volume is set to 0 @@ -72,7 +72,7 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str, pair: 'close': 'last', 'volume': 'sum' } - ticker_minutes = timeframe_to_minutes(ticker_interval) + ticker_minutes = timeframe_to_minutes(timeframe) # Resample to create "NAN" values df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index f0787281a..ce4554cbb 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -42,29 +42,29 @@ class DataProvider: """ return list(self._exchange._klines.keys()) - def ohlcv(self, pair: str, ticker_interval: str = None, copy: bool = True) -> DataFrame: + def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame: """ Get ohlcv data for the given pair as DataFrame Please use the `available_pairs` method to verify which pairs are currently cached. :param pair: pair to get the data for - :param ticker_interval: ticker interval to get data for + :param timeframe: Ticker timeframe to get data for :param copy: copy dataframe before returning if True. Use False only for read-only operations (where the dataframe is not modified) """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): - return self._exchange.klines((pair, ticker_interval or self._config['ticker_interval']), + return self._exchange.klines((pair, timeframe or self._config['ticker_interval']), copy=copy) else: return DataFrame() - def historic_ohlcv(self, pair: str, ticker_interval: str = None) -> DataFrame: + def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame: """ Get stored historic ohlcv data :param pair: pair to get the data for - :param ticker_interval: ticker interval to get data for + :param timeframe: ticker interval to get data for """ return load_pair_history(pair=pair, - ticker_interval=ticker_interval or self._config['ticker_interval'], + timeframe=timeframe or self._config['ticker_interval'], datadir=Path(self._config['datadir']) ) @@ -77,10 +77,10 @@ class DataProvider: """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): # Get live ohlcv data. - data = self.ohlcv(pair=pair, ticker_interval=ticker_interval) + data = self.ohlcv(pair=pair, timeframe=ticker_interval) else: # Get historic ohlcv data (cached on disk). - data = self.historic_ohlcv(pair=pair, ticker_interval=ticker_interval) + data = self.historic_ohlcv(pair=pair, timeframe=ticker_interval) if len(data) == 0: logger.warning(f"No data found for ({pair}, {ticker_interval}).") return data diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 3dd40d2b4..8e4bc8ced 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -63,13 +63,13 @@ def trim_dataframe(df: DataFrame, timerange: TimeRange) -> DataFrame: return df -def load_tickerdata_file(datadir: Path, pair: str, ticker_interval: str, +def load_tickerdata_file(datadir: Path, pair: str, timeframe: str, timerange: Optional[TimeRange] = None) -> Optional[list]: """ Load a pair from file, either .json.gz or .json :return: tickerlist or None if unsuccessful """ - filename = pair_data_filename(datadir, pair, ticker_interval) + filename = pair_data_filename(datadir, pair, timeframe) pairdata = misc.file_load_json(filename) if not pairdata: return [] @@ -80,11 +80,11 @@ def load_tickerdata_file(datadir: Path, pair: str, ticker_interval: str, def store_tickerdata_file(datadir: Path, pair: str, - ticker_interval: str, data: list, is_zip: bool = False): + timeframe: str, data: list, is_zip: bool = False): """ Stores tickerdata to file """ - filename = pair_data_filename(datadir, pair, ticker_interval) + filename = pair_data_filename(datadir, pair, timeframe) misc.file_dump_json(filename, data, is_zip=is_zip) @@ -121,7 +121,7 @@ def _validate_pairdata(pair, pairdata, timerange: TimeRange): def load_pair_history(pair: str, - ticker_interval: str, + timeframe: str, datadir: Path, timerange: Optional[TimeRange] = None, refresh_pairs: bool = False, @@ -133,7 +133,7 @@ def load_pair_history(pair: str, """ Loads cached ticker history for the given pair. :param pair: Pair to load data for - :param ticker_interval: Ticker-interval (e.g. "5m") + :param timeframe: Ticker timeframe (e.g. "5m") :param datadir: Path to the data storage location. :param timerange: Limit data to be loaded to this timerange :param refresh_pairs: Refresh pairs from exchange. @@ -147,34 +147,34 @@ def load_pair_history(pair: str, timerange_startup = deepcopy(timerange) if startup_candles > 0 and timerange_startup: - timerange_startup.subtract_start(timeframe_to_seconds(ticker_interval) * startup_candles) + timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles) # The user forced the refresh of pairs if refresh_pairs: download_pair_history(datadir=datadir, exchange=exchange, pair=pair, - ticker_interval=ticker_interval, + timeframe=timeframe, timerange=timerange) - pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange_startup) + pairdata = load_tickerdata_file(datadir, pair, timeframe, timerange=timerange_startup) if pairdata: if timerange_startup: _validate_pairdata(pair, pairdata, timerange_startup) - return parse_ticker_dataframe(pairdata, ticker_interval, pair=pair, + return parse_ticker_dataframe(pairdata, timeframe, pair=pair, fill_missing=fill_up_missing, drop_incomplete=drop_incomplete) else: logger.warning( - f'No history data for pair: "{pair}", interval: {ticker_interval}. ' + f'No history data for pair: "{pair}", timeframe: {timeframe}. ' 'Use `freqtrade download-data` to download the data' ) return None def load_data(datadir: Path, - ticker_interval: str, + timeframe: str, pairs: List[str], refresh_pairs: bool = False, exchange: Optional[Exchange] = None, @@ -186,7 +186,7 @@ def load_data(datadir: Path, """ Loads ticker history data for a list of pairs :param datadir: Path to the data storage location. - :param ticker_interval: Ticker-interval (e.g. "5m") + :param timeframe: Ticker Timeframe (e.g. "5m") :param pairs: List of pairs to load :param refresh_pairs: Refresh pairs from exchange. (Note: Requires exchange to be passed as well.) @@ -206,7 +206,7 @@ def load_data(datadir: Path, logger.info(f'Using indicator startup period: {startup_candles} ...') for pair in pairs: - hist = load_pair_history(pair=pair, ticker_interval=ticker_interval, + hist = load_pair_history(pair=pair, timeframe=timeframe, datadir=datadir, timerange=timerange, refresh_pairs=refresh_pairs, exchange=exchange, @@ -220,9 +220,9 @@ def load_data(datadir: Path, return result -def pair_data_filename(datadir: Path, pair: str, ticker_interval: str) -> Path: +def pair_data_filename(datadir: Path, pair: str, timeframe: str) -> Path: pair_s = pair.replace("/", "_") - filename = datadir.joinpath(f'{pair_s}-{ticker_interval}.json') + filename = datadir.joinpath(f'{pair_s}-{timeframe}.json') return filename @@ -232,7 +232,7 @@ def pair_trades_filename(datadir: Path, pair: str) -> Path: return filename -def _load_cached_data_for_updating(datadir: Path, pair: str, ticker_interval: str, +def _load_cached_data_for_updating(datadir: Path, pair: str, timeframe: str, timerange: Optional[TimeRange]) -> Tuple[List[Any], Optional[int]]: """ @@ -250,12 +250,12 @@ def _load_cached_data_for_updating(datadir: Path, pair: str, ticker_interval: st if timerange.starttype == 'date': since_ms = timerange.startts * 1000 elif timerange.stoptype == 'line': - num_minutes = timerange.stopts * timeframe_to_minutes(ticker_interval) + num_minutes = timerange.stopts * timeframe_to_minutes(timeframe) since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000 # read the cached file # Intentionally don't pass timerange in - since we need to load the full dataset. - data = load_tickerdata_file(datadir, pair, ticker_interval) + data = load_tickerdata_file(datadir, pair, timeframe) # remove the last item, could be incomplete candle if data: data.pop() @@ -276,18 +276,18 @@ def _load_cached_data_for_updating(datadir: Path, pair: str, ticker_interval: st def download_pair_history(datadir: Path, exchange: Optional[Exchange], pair: str, - ticker_interval: str = '5m', + timeframe: str = '5m', timerange: Optional[TimeRange] = None) -> bool: """ Download the latest ticker intervals from the exchange for the pair passed in parameters - The data is downloaded starting from the last correct ticker interval data that + The data is downloaded starting from the last correct data that exists in a cache. If timerange starts earlier than the data in the cache, the full data will be redownloaded Based on @Rybolov work: https://github.com/rybolov/freqtrade-data :param pair: pair to download - :param ticker_interval: ticker interval + :param timeframe: Ticker Timeframe (e.g 5m) :param timerange: range of time to download :return: bool with success state """ @@ -298,17 +298,17 @@ def download_pair_history(datadir: Path, try: logger.info( - f'Download history data for pair: "{pair}", interval: {ticker_interval} ' + f'Download history data for pair: "{pair}", timeframe: {timeframe} ' f'and store in {datadir}.' ) - data, since_ms = _load_cached_data_for_updating(datadir, pair, ticker_interval, timerange) + data, since_ms = _load_cached_data_for_updating(datadir, pair, timeframe, timerange) logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None') logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None') # Default since_ms to 30 days if nothing is given - new_data = exchange.get_historic_ohlcv(pair=pair, ticker_interval=ticker_interval, + new_data = exchange.get_historic_ohlcv(pair=pair, ticker_interval=timeframe, since_ms=since_ms if since_ms else int(arrow.utcnow().shift( @@ -318,12 +318,12 @@ def download_pair_history(datadir: Path, logger.debug("New Start: %s", misc.format_ms_time(data[0][0])) logger.debug("New End: %s", misc.format_ms_time(data[-1][0])) - store_tickerdata_file(datadir, pair, ticker_interval, data=data) + store_tickerdata_file(datadir, pair, timeframe, data=data) return True except Exception as e: logger.error( - f'Failed to download history data for pair: "{pair}", interval: {ticker_interval}. ' + f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}. ' f'Error: {e}' ) return False @@ -343,17 +343,17 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes pairs_not_available.append(pair) logger.info(f"Skipping pair {pair}...") continue - for ticker_interval in timeframes: + for timeframe in timeframes: - dl_file = pair_data_filename(dl_path, pair, ticker_interval) + dl_file = pair_data_filename(dl_path, pair, timeframe) if erase and dl_file.exists(): logger.info( - f'Deleting existing data for pair {pair}, interval {ticker_interval}.') + f'Deleting existing data for pair {pair}, interval {timeframe}.') dl_file.unlink() - logger.info(f'Downloading pair {pair}, interval {ticker_interval}.') + logger.info(f'Downloading pair {pair}, interval {timeframe}.') download_pair_history(datadir=dl_path, exchange=exchange, - pair=pair, ticker_interval=str(ticker_interval), + pair=pair, timeframe=str(timeframe), timerange=timerange) return pairs_not_available @@ -459,7 +459,7 @@ def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow] def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime, - max_date: datetime, ticker_interval_mins: int) -> bool: + max_date: datetime, timeframe_mins: int) -> bool: """ Validates preprocessed backtesting data for missing values and shows warnings about it that. @@ -467,10 +467,10 @@ def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime, :param pair: pair used for log output. :param min_date: start-date of the data :param max_date: end-date of the data - :param ticker_interval_mins: ticker interval in minutes + :param timeframe_mins: ticker Timeframe in minutes """ - # total difference in minutes / interval-minutes - expected_frames = int((max_date - min_date).total_seconds() // 60 // ticker_interval_mins) + # total difference in minutes / timeframe-minutes + expected_frames = int((max_date - min_date).total_seconds() // 60 // timeframe_mins) found_missing = False dflen = len(data) if dflen < expected_frames: diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 883bf4a0f..afd20cf61 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -97,7 +97,7 @@ class Edge: data = history.load_data( datadir=Path(self.config['datadir']), pairs=pairs, - ticker_interval=self.strategy.ticker_interval, + timeframe=self.strategy.ticker_interval, refresh_pairs=self._refresh_pairs, exchange=self.exchange, timerange=self._timerange, diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index ee3a135d2..58fd1f772 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -108,7 +108,7 @@ class Backtesting: data = history.load_data( datadir=Path(self.config['datadir']), pairs=self.config['exchange']['pair_whitelist'], - ticker_interval=self.ticker_interval, + timeframe=self.ticker_interval, timerange=timerange, startup_candles=self.required_startup, fail_without_data=True, diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index bbdb52ca1..01396aea9 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -39,7 +39,7 @@ def init_plotscript(config): tickers = history.load_data( datadir=Path(str(config.get("datadir"))), pairs=pairs, - ticker_interval=config.get('ticker_interval', '5m'), + timeframe=config.get('ticker_interval', '5m'), timerange=timerange, ) @@ -300,12 +300,12 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame], return fig -def generate_plot_filename(pair, ticker_interval) -> str: +def generate_plot_filename(pair, timeframe) -> str: """ - Generate filenames per pair/ticker_interval to be used for storing plots + Generate filenames per pair/timeframe to be used for storing plots """ pair_name = pair.replace("/", "_") - file_name = 'freqtrade-plot-' + pair_name + '-' + ticker_interval + '.html' + file_name = 'freqtrade-plot-' + pair_name + '-' + timeframe + '.html' logger.info('Generate plot file for %s', pair) @@ -316,8 +316,9 @@ def store_plot_file(fig, filename: str, directory: Path, auto_open: bool = False """ Generate a plot html file from pre populated fig plotly object :param fig: Plotly Figure to plot - :param pair: Pair to plot (used as filename and Plot title) - :param ticker_interval: Used as part of the filename + :param filename: Name to store the file as + :param directory: Directory to store the file in + :param auto_open: Automatically open files saved :return: None """ directory.mkdir(parents=True, exist_ok=True) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index b49344bbd..13711c63e 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -56,7 +56,7 @@ def test_extract_trades_of_period(testdatadir): # 2018-11-14 06:07:00 timerange = TimeRange('date', None, 1510639620, 0) - data = load_pair_history(pair=pair, ticker_interval='1m', + data = load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) trades = DataFrame( @@ -122,7 +122,7 @@ def test_combine_tickers_with_mean(testdatadir): pairs = ["ETH/BTC", "ADA/BTC"] tickers = load_data(datadir=testdatadir, pairs=pairs, - ticker_interval='5m' + timeframe='5m' ) df = combine_tickers_with_mean(tickers) assert isinstance(df, DataFrame) @@ -136,7 +136,7 @@ def test_create_cum_profit(testdatadir): bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") - df = load_pair_history(pair="TRX/BTC", ticker_interval='5m', + df = load_pair_history(pair="TRX/BTC", timeframe='5m', datadir=testdatadir, timerange=timerange) cum_profits = create_cum_profit(df.set_index('date'), @@ -154,7 +154,7 @@ def test_create_cum_profit1(testdatadir): bt_data.loc[:, 'close_time'] = bt_data.loc[:, 'close_time'] + DateOffset(seconds=20) timerange = TimeRange.parse_timerange("20180110-20180112") - df = load_pair_history(pair="TRX/BTC", ticker_interval='5m', + df = load_pair_history(pair="TRX/BTC", timeframe='5m', datadir=testdatadir, timerange=timerange) cum_profits = create_cum_profit(df.set_index('date'), diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index e773a970e..92494ff1e 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -23,7 +23,7 @@ def test_parse_ticker_dataframe(ticker_history_list, caplog): def test_ohlcv_fill_up_missing_data(testdatadir, caplog): data = load_pair_history(datadir=testdatadir, - ticker_interval='1m', + timeframe='1m', pair='UNITTEST/BTC', fill_up_missing=False) caplog.set_level(logging.DEBUG) diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index 9a857750b..0318e5a82 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -45,7 +45,7 @@ def test_historic_ohlcv(mocker, default_conf, ticker_history): data = dp.historic_ohlcv("UNITTEST/BTC", "5m") assert isinstance(data, DataFrame) assert historymock.call_count == 1 - assert historymock.call_args_list[0][1]["ticker_interval"] == "5m" + assert historymock.call_args_list[0][1]["timeframe"] == "5m" def test_get_pair_dataframe(mocker, default_conf, ticker_history): diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 89120b4f5..65feaf03e 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -64,20 +64,20 @@ def _clean_test_file(file: Path) -> None: def test_load_data_30min_ticker(mocker, caplog, default_conf, testdatadir) -> None: - ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='30m', datadir=testdatadir) + ld = history.load_pair_history(pair='UNITTEST/BTC', timeframe='30m', datadir=testdatadir) assert isinstance(ld, DataFrame) assert not log_has( - 'Download history data for pair: "UNITTEST/BTC", interval: 30m ' + 'Download history data for pair: "UNITTEST/BTC", timeframe: 30m ' 'and store in None.', caplog ) def test_load_data_7min_ticker(mocker, caplog, default_conf, testdatadir) -> None: - ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='7m', datadir=testdatadir) + ld = history.load_pair_history(pair='UNITTEST/BTC', timeframe='7m', datadir=testdatadir) assert not isinstance(ld, DataFrame) assert ld is None assert log_has( - 'No history data for pair: "UNITTEST/BTC", interval: 7m. ' + 'No history data for pair: "UNITTEST/BTC", timeframe: 7m. ' 'Use `freqtrade download-data` to download the data', caplog ) @@ -86,7 +86,7 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog, testdatadir) -> N mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history) file = testdatadir / 'UNITTEST_BTC-1m.json' _backup_file(file, copy_file=True) - history.load_data(datadir=testdatadir, ticker_interval='1m', pairs=['UNITTEST/BTC']) + history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC']) assert file.is_file() assert not log_has( 'Download history data for pair: "UNITTEST/BTC", interval: 1m ' @@ -99,7 +99,7 @@ def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> ltfmock = mocker.patch('freqtrade.data.history.load_tickerdata_file', MagicMock(return_value=None)) timerange = TimeRange('date', None, 1510639620, 0) - history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='1m', + history.load_pair_history(pair='UNITTEST/BTC', timeframe='1m', datadir=testdatadir, timerange=timerange, startup_candles=20, ) @@ -122,28 +122,28 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, _backup_file(file) # do not download a new pair if refresh_pairs isn't set history.load_pair_history(datadir=testdatadir, - ticker_interval='1m', + timeframe='1m', pair='MEME/BTC') assert not file.is_file() assert log_has( - 'No history data for pair: "MEME/BTC", interval: 1m. ' + 'No history data for pair: "MEME/BTC", timeframe: 1m. ' 'Use `freqtrade download-data` to download the data', caplog ) # download a new pair if refresh_pairs is set history.load_pair_history(datadir=testdatadir, - ticker_interval='1m', + timeframe='1m', refresh_pairs=True, exchange=exchange, pair='MEME/BTC') assert file.is_file() assert log_has_re( - 'Download history data for pair: "MEME/BTC", interval: 1m ' + 'Download history data for pair: "MEME/BTC", timeframe: 1m ' 'and store in .*', caplog ) with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'): history.load_pair_history(datadir=testdatadir, - ticker_interval='1m', + timeframe='1m', refresh_pairs=True, exchange=None, pair='MEME/BTC') @@ -269,10 +269,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf, testda assert download_pair_history(datadir=testdatadir, exchange=exchange, pair='MEME/BTC', - ticker_interval='1m') + timeframe='1m') assert download_pair_history(datadir=testdatadir, exchange=exchange, pair='CFI/BTC', - ticker_interval='1m') + timeframe='1m') assert not exchange._pairs_last_refresh_time assert file1_1.is_file() assert file2_1.is_file() @@ -286,10 +286,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf, testda assert download_pair_history(datadir=testdatadir, exchange=exchange, pair='MEME/BTC', - ticker_interval='5m') + timeframe='5m') assert download_pair_history(datadir=testdatadir, exchange=exchange, pair='CFI/BTC', - ticker_interval='5m') + timeframe='5m') assert not exchange._pairs_last_refresh_time assert file1_5.is_file() assert file2_5.is_file() @@ -307,8 +307,8 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None: json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None) mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick) exchange = get_patched_exchange(mocker, default_conf) - download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", ticker_interval='1m') - download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", ticker_interval='3m') + download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='1m') + download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='3m') assert json_dump_mock.call_count == 2 @@ -326,12 +326,12 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog, assert not download_pair_history(datadir=testdatadir, exchange=exchange, pair='MEME/BTC', - ticker_interval='1m') + timeframe='1m') # clean files freshly downloaded _clean_test_file(file1_1) _clean_test_file(file1_5) assert log_has( - 'Failed to download history data for pair: "MEME/BTC", interval: 1m. ' + 'Failed to download history data for pair: "MEME/BTC", timeframe: 1m. ' 'Error: File Error', caplog ) @@ -369,7 +369,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: caplog.clear() start = arrow.get('2018-01-10T00:00:00') end = arrow.get('2018-02-20T00:00:00') - tickerdata = history.load_data(datadir=testdatadir, ticker_interval='5m', + tickerdata = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) @@ -390,7 +390,7 @@ def test_init(default_conf, mocker) -> None: exchange=exchange, pairs=[], refresh_pairs=True, - ticker_interval=default_conf['ticker_interval'] + timeframe=default_conf['ticker_interval'] ) @@ -449,7 +449,7 @@ def test_trim_tickerlist(testdatadir) -> None: def test_trim_dataframe(testdatadir) -> None: data = history.load_data( datadir=testdatadir, - ticker_interval='1m', + timeframe='1m', pairs=['UNITTEST/BTC'] )['UNITTEST/BTC'] min_date = int(data.iloc[0]['date'].timestamp()) @@ -517,7 +517,7 @@ def test_get_timeframe(default_conf, mocker, testdatadir) -> None: data = strategy.tickerdata_to_dataframe( history.load_data( datadir=testdatadir, - ticker_interval='1m', + timeframe='1m', pairs=['UNITTEST/BTC'] ) ) @@ -533,7 +533,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) data = strategy.tickerdata_to_dataframe( history.load_data( datadir=testdatadir, - ticker_interval='1m', + timeframe='1m', pairs=['UNITTEST/BTC'], fill_up_missing=False ) @@ -556,7 +556,7 @@ def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> No data = strategy.tickerdata_to_dataframe( history.load_data( datadir=testdatadir, - ticker_interval='5m', + timeframe='5m', pairs=['UNITTEST/BTC'], timerange=timerange ) @@ -669,10 +669,10 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog): file5 = testdatadir / 'XRP_ETH-5m.json' # Compare downloaded dataset with converted dataset dfbak_1m = history.load_pair_history(datadir=testdatadir, - ticker_interval="1m", + timeframe="1m", pair=pair) dfbak_5m = history.load_pair_history(datadir=testdatadir, - ticker_interval="5m", + timeframe="5m", pair=pair) _backup_file(file1, copy_file=True) @@ -686,10 +686,10 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog): assert log_has("Deleting existing data for pair XRP/ETH, interval 1m.", caplog) # Load new data df_1m = history.load_pair_history(datadir=testdatadir, - ticker_interval="1m", + timeframe="1m", pair=pair) df_5m = history.load_pair_history(datadir=testdatadir, - ticker_interval="5m", + timeframe="5m", pair=pair) assert df_1m.equals(dfbak_1m) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index e1af50768..001dc9591 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -255,7 +255,7 @@ def test_edge_heartbeat_calculate(mocker, edge_conf): assert edge.calculate() is False -def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False, +def mocked_load_data(datadir, pairs=[], timeframe='0m', refresh_pairs=False, timerange=None, exchange=None, *args, **kwargs): hz = 0.1 base = 0.001 diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 5912c5489..a5ab6d84c 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -50,7 +50,7 @@ def trim_dictlist(dict_list, num): def load_data_test(what, testdatadir): timerange = TimeRange.parse_timerange('1510694220-1510700340') - pair = history.load_tickerdata_file(testdatadir, ticker_interval='1m', + pair = history.load_tickerdata_file(testdatadir, timeframe='1m', pair='UNITTEST/BTC', timerange=timerange) datalen = len(pair) @@ -116,7 +116,7 @@ def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None: assert len(results) == num_results -def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False, +def mocked_load_data(datadir, pairs=[], timeframe='0m', refresh_pairs=False, timerange=None, exchange=None, live=False, *args, **kwargs): tickerdata = history.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange) pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata, '1m', pair="UNITTEST/BTC", @@ -126,14 +126,14 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals # use for mock ccxt.fetch_ohlvc' def _load_pair_as_ticks(pair, tickfreq): - ticks = history.load_tickerdata_file(None, ticker_interval=tickfreq, pair=pair) + ticks = history.load_tickerdata_file(None, timeframe=tickfreq, pair=pair) ticks = ticks[-201:] return ticks # FIX: fixturize this? def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC', record=None): - data = history.load_data(datadir=datadir, ticker_interval='1m', pairs=[pair]) + data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair]) data = trim_dictlist(data, -201) patch_exchange(mocker) backtesting = Backtesting(conf) @@ -522,7 +522,7 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None: backtesting = Backtesting(default_conf) pair = 'UNITTEST/BTC' timerange = TimeRange('date', None, 1517227800, 0) - data = history.load_data(datadir=testdatadir, ticker_interval='5m', pairs=['UNITTEST/BTC'], + data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], timerange=timerange) data_processed = backtesting.strategy.tickerdata_to_dataframe(data) min_date, max_date = get_timeframe(data_processed) @@ -576,9 +576,9 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) - patch_exchange(mocker) backtesting = Backtesting(default_conf) - # Run a backtesting for an exiting 1min ticker_interval + # Run a backtesting for an exiting 1min timeframe timerange = TimeRange.parse_timerange('1510688220-1510700340') - data = history.load_data(datadir=testdatadir, ticker_interval='1m', pairs=['UNITTEST/BTC'], + data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'], timerange=timerange) processed = backtesting.strategy.tickerdata_to_dataframe(data) min_date, max_date = get_timeframe(processed) @@ -688,7 +688,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) patch_exchange(mocker) pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC'] - data = history.load_data(datadir=testdatadir, ticker_interval='5m', pairs=pairs) + data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=pairs) # Only use 500 lines to increase performance data = trim_dictlist(data, -500) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 4a6efcd8e..f0d9578ac 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -64,7 +64,7 @@ def test_add_indicators(default_conf, testdatadir, caplog): pair = "UNITTEST/BTC" timerange = TimeRange(None, 'line', 0, -1000) - data = history.load_pair_history(pair=pair, ticker_interval='1m', + data = history.load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) indicators1 = ["ema10"] indicators2 = ["macd"] @@ -129,7 +129,7 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, t pair = "UNITTEST/BTC" timerange = TimeRange(None, 'line', 0, -1000) - data = history.load_pair_history(pair=pair, ticker_interval='1m', + data = history.load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) data['buy'] = 0 data['sell'] = 0 @@ -164,7 +164,7 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir) MagicMock(side_effect=fig_generating_mock)) pair = 'UNITTEST/BTC' timerange = TimeRange(None, 'line', 0, -1000) - data = history.load_pair_history(pair=pair, ticker_interval='1m', + data = history.load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) # Generate buy/sell signals and indicators @@ -228,7 +228,7 @@ def test_add_profit(testdatadir): bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") - df = history.load_pair_history(pair="TRX/BTC", ticker_interval='5m', + df = history.load_pair_history(pair="TRX/BTC", timeframe='5m', datadir=testdatadir, timerange=timerange) fig = generate_empty_figure() @@ -251,7 +251,7 @@ def test_generate_profit_graph(testdatadir): tickers = history.load_data(datadir=testdatadir, pairs=pairs, - ticker_interval='5m', + timeframe='5m', timerange=timerange ) trades = trades[trades['pair'].isin(pairs)] From 08aedc18e1eb2bf7ff0ead368ec043c5585c17f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Nov 2019 20:25:18 +0100 Subject: [PATCH 106/157] Exchange ticker_interval with timeframe in some more places --- freqtrade/data/dataprovider.py | 13 ++++--- freqtrade/data/history.py | 2 +- freqtrade/exchange/exchange.py | 67 ++++++++++++++++----------------- tests/exchange/test_exchange.py | 4 +- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index ce4554cbb..db71ff029 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -37,7 +37,7 @@ class DataProvider: @property def available_pairs(self) -> List[Tuple[str, str]]: """ - Return a list of tuples containing pair, ticker_interval for which data is currently cached. + Return a list of tuples containing (pair, timeframe) for which data is currently cached. Should be whitelist + open trades. """ return list(self._exchange._klines.keys()) @@ -68,21 +68,22 @@ class DataProvider: datadir=Path(self._config['datadir']) ) - def get_pair_dataframe(self, pair: str, ticker_interval: str = None) -> DataFrame: + def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame: """ Return pair ohlcv data, either live or cached historical -- depending on the runmode. :param pair: pair to get the data for - :param ticker_interval: ticker interval to get data for + :param timeframe: ticker interval to get data for + :return: Dataframe for this pair """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): # Get live ohlcv data. - data = self.ohlcv(pair=pair, timeframe=ticker_interval) + data = self.ohlcv(pair=pair, timeframe=timeframe) else: # Get historic ohlcv data (cached on disk). - data = self.historic_ohlcv(pair=pair, timeframe=ticker_interval) + data = self.historic_ohlcv(pair=pair, timeframe=timeframe) if len(data) == 0: - logger.warning(f"No data found for ({pair}, {ticker_interval}).") + logger.warning(f"No data found for ({pair}, {timeframe}).") return data def market(self, pair: str) -> Optional[Dict[str, Any]]: diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 8e4bc8ced..3dea41c55 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -308,7 +308,7 @@ def download_pair_history(datadir: Path, logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None') # Default since_ms to 30 days if nothing is given - new_data = exchange.get_historic_ohlcv(pair=pair, ticker_interval=timeframe, + new_data = exchange.get_historic_ohlcv(pair=pair, timeframe=timeframe, since_ms=since_ms if since_ms else int(arrow.utcnow().shift( diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a198e8cdb..05db45c9b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -536,40 +536,40 @@ class Exchange: logger.info("returning cached ticker-data for %s", pair) return self._cached_ticker[pair] - def get_historic_ohlcv(self, pair: str, ticker_interval: str, + def get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int) -> List: """ Gets candle history using asyncio and returns the list of candles. Handles all async doing. Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call. :param pair: Pair to download - :param ticker_interval: Interval to get + :param timeframe: Ticker Timeframe to get :param since_ms: Timestamp in milliseconds to get history from :returns List of tickers """ return asyncio.get_event_loop().run_until_complete( - self._async_get_historic_ohlcv(pair=pair, ticker_interval=ticker_interval, + self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, since_ms=since_ms)) async def _async_get_historic_ohlcv(self, pair: str, - ticker_interval: str, + timeframe: str, since_ms: int) -> List: - one_call = timeframe_to_msecs(ticker_interval) * self._ohlcv_candle_limit + one_call = timeframe_to_msecs(timeframe) * self._ohlcv_candle_limit logger.debug( "one_call: %s msecs (%s)", one_call, arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True) ) input_coroutines = [self._async_get_candle_history( - pair, ticker_interval, since) for since in + pair, timeframe, since) for since in range(since_ms, arrow.utcnow().timestamp * 1000, one_call)] tickers = await asyncio.gather(*input_coroutines, return_exceptions=True) # Combine tickers data: List = [] - for p, ticker_interval, ticker in tickers: + for p, timeframe, ticker in tickers: if p == pair: data.extend(ticker) # Sort data again after extending the result - above calls return in "async order" @@ -589,14 +589,14 @@ class Exchange: input_coroutines = [] # Gather coroutines to run - for pair, ticker_interval in set(pair_list): - if (not ((pair, ticker_interval) in self._klines) - or self._now_is_time_to_refresh(pair, ticker_interval)): - input_coroutines.append(self._async_get_candle_history(pair, ticker_interval)) + for pair, timeframe in set(pair_list): + if (not ((pair, timeframe) in self._klines) + or self._now_is_time_to_refresh(pair, timeframe)): + input_coroutines.append(self._async_get_candle_history(pair, timeframe)) else: logger.debug( - "Using cached ohlcv data for pair %s, interval %s ...", - pair, ticker_interval + "Using cached ohlcv data for pair %s, timeframe %s ...", + pair, timeframe ) tickers = asyncio.get_event_loop().run_until_complete( @@ -608,40 +608,40 @@ class Exchange: logger.warning("Async code raised an exception: %s", res.__class__.__name__) continue pair = res[0] - ticker_interval = res[1] + timeframe = res[1] ticks = res[2] # keeping last candle time as last refreshed time of the pair if ticks: - self._pairs_last_refresh_time[(pair, ticker_interval)] = ticks[-1][0] // 1000 + self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000 # keeping parsed dataframe in cache - self._klines[(pair, ticker_interval)] = parse_ticker_dataframe( - ticks, ticker_interval, pair=pair, fill_missing=True, + self._klines[(pair, timeframe)] = parse_ticker_dataframe( + ticks, timeframe, pair=pair, fill_missing=True, drop_incomplete=self._ohlcv_partial_candle) return tickers - def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool: + def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool: # Calculating ticker interval in seconds - interval_in_sec = timeframe_to_seconds(ticker_interval) + interval_in_sec = timeframe_to_seconds(timeframe) - return not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0) + return not ((self._pairs_last_refresh_time.get((pair, timeframe), 0) + interval_in_sec) >= arrow.utcnow().timestamp) @retrier_async - async def _async_get_candle_history(self, pair: str, ticker_interval: str, + async def _async_get_candle_history(self, pair: str, timeframe: str, since_ms: Optional[int] = None) -> Tuple[str, str, List]: """ Asynchronously gets candle histories using fetch_ohlcv - returns tuple: (pair, ticker_interval, ohlcv_list) + returns tuple: (pair, timeframe, ohlcv_list) """ try: # fetch ohlcv asynchronously s = '(' + arrow.get(since_ms // 1000).isoformat() + ') ' if since_ms is not None else '' logger.debug( "Fetching pair %s, interval %s, since %s %s...", - pair, ticker_interval, since_ms, s + pair, timeframe, since_ms, s ) - data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval, + data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe, since=since_ms) # Because some exchange sort Tickers ASC and other DESC. @@ -653,9 +653,9 @@ class Exchange: data = sorted(data, key=lambda x: x[0]) except IndexError: logger.exception("Error loading %s. Result was %s.", pair, data) - return pair, ticker_interval, [] - logger.debug("Done fetching pair %s, interval %s ...", pair, ticker_interval) - return pair, ticker_interval, data + return pair, timeframe, [] + logger.debug("Done fetching pair %s, interval %s ...", pair, timeframe) + return pair, timeframe, data except ccxt.NotSupported as e: raise OperationalException( @@ -802,7 +802,6 @@ class Exchange: Handles all async doing. Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call. :param pair: Pair to download - :param ticker_interval: Interval to get :param since: Timestamp in milliseconds to get history from :param until: Timestamp in milliseconds. Defaults to current timestamp if not defined. :param from_id: Download data starting with ID (if id is known) @@ -958,27 +957,27 @@ def available_exchanges(ccxt_module=None) -> List[str]: return [x for x in exchanges if not is_exchange_bad(x)] -def timeframe_to_seconds(ticker_interval: str) -> int: +def timeframe_to_seconds(timeframe: str) -> int: """ Translates the timeframe interval value written in the human readable form ('1m', '5m', '1h', '1d', '1w', etc.) to the number of seconds for one timeframe interval. """ - return ccxt.Exchange.parse_timeframe(ticker_interval) + return ccxt.Exchange.parse_timeframe(timeframe) -def timeframe_to_minutes(ticker_interval: str) -> int: +def timeframe_to_minutes(timeframe: str) -> int: """ Same as timeframe_to_seconds, but returns minutes. """ - return ccxt.Exchange.parse_timeframe(ticker_interval) // 60 + return ccxt.Exchange.parse_timeframe(timeframe) // 60 -def timeframe_to_msecs(ticker_interval: str) -> int: +def timeframe_to_msecs(timeframe: str) -> int: """ Same as timeframe_to_seconds, but returns milliseconds. """ - return ccxt.Exchange.parse_timeframe(ticker_interval) * 1000 + return ccxt.Exchange.parse_timeframe(timeframe) * 1000 def timeframe_to_prev_date(timeframe: str, date: datetime = None) -> datetime: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 925a53c95..68fac8632 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1107,7 +1107,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]) assert exchange._api_async.fetch_ohlcv.call_count == 2 - assert log_has(f"Using cached ohlcv data for pair {pairs[0][0]}, interval {pairs[0][1]} ...", + assert log_has(f"Using cached ohlcv data for pair {pairs[0][0]}, timeframe {pairs[0][1]} ...", caplog) @@ -1143,7 +1143,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ # exchange = Exchange(default_conf) await async_ccxt_exception(mocker, default_conf, MagicMock(), "_async_get_candle_history", "fetch_ohlcv", - pair='ABCD/BTC', ticker_interval=default_conf['ticker_interval']) + pair='ABCD/BTC', timeframe=default_conf['ticker_interval']) api_mock = MagicMock() with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): From d801dec6aa45fb1eb8271ad8166e9b44a9608e53 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Nov 2019 20:26:26 +0100 Subject: [PATCH 107/157] Some more places with ticker_interval gone --- freqtrade/optimize/backtesting.py | 10 +++++----- tests/data/test_converter.py | 12 ++++++------ tests/data/test_dataprovider.py | 24 ++++++++++++------------ tests/exchange/test_exchange.py | 4 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 58fd1f772..79478076b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -83,8 +83,8 @@ class Backtesting: if "ticker_interval" not in self.config: raise OperationalException("Ticker-interval needs to be set in either configuration " "or as cli argument `--ticker-interval 5m`") - self.ticker_interval = str(self.config.get('ticker_interval')) - self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval) + self.timeframe = str(self.config.get('ticker_interval')) + self.timeframe_mins = timeframe_to_minutes(self.timeframe) # Get maximum required startup period self.required_startup = max([strat.startup_candle_count for strat in self.strategylist]) @@ -108,7 +108,7 @@ class Backtesting: data = history.load_data( datadir=Path(self.config['datadir']), pairs=self.config['exchange']['pair_whitelist'], - timeframe=self.ticker_interval, + timeframe=self.timeframe, timerange=timerange, startup_candles=self.required_startup, fail_without_data=True, @@ -375,7 +375,7 @@ class Backtesting: lock_pair_until: Dict = {} # Indexes per pair, so some pairs are allowed to have a missing start. indexes: Dict = {} - tmp = start_date + timedelta(minutes=self.ticker_interval_mins) + tmp = start_date + timedelta(minutes=self.timeframe_mins) # Loop timerange and get candle for each pair at that point in time while tmp < end_date: @@ -427,7 +427,7 @@ class Backtesting: lock_pair_until[pair] = end_date.datetime # Move time one configured time_interval ahead. - tmp += timedelta(minutes=self.ticker_interval_mins) + tmp += timedelta(minutes=self.timeframe_mins) return DataFrame.from_records(trades, columns=BacktestResult._fields) def start(self) -> None: diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index 92494ff1e..8184167b3 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -42,7 +42,7 @@ def test_ohlcv_fill_up_missing_data(testdatadir, caplog): def test_ohlcv_fill_up_missing_data2(caplog): - ticker_interval = '5m' + timeframe = '5m' ticks = [[ 1511686200000, # 8:50:00 8.794e-05, # open @@ -78,10 +78,10 @@ def test_ohlcv_fill_up_missing_data2(caplog): ] # Generate test-data without filling missing - data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", fill_missing=False) + data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC", fill_missing=False) assert len(data) == 3 caplog.set_level(logging.DEBUG) - data2 = ohlcv_fill_up_missing_data(data, ticker_interval, "UNITTEST/BTC") + data2 = ohlcv_fill_up_missing_data(data, timeframe, "UNITTEST/BTC") assert len(data2) == 4 # 3rd candle has been filled row = data2.loc[2, :] @@ -99,7 +99,7 @@ def test_ohlcv_fill_up_missing_data2(caplog): def test_ohlcv_drop_incomplete(caplog): - ticker_interval = '1d' + timeframe = '1d' ticks = [[ 1559750400000, # 2019-06-04 8.794e-05, # open @@ -134,13 +134,13 @@ def test_ohlcv_drop_incomplete(caplog): ] ] caplog.set_level(logging.DEBUG) - data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", + data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC", fill_missing=False, drop_incomplete=False) assert len(data) == 4 assert not log_has("Dropping last candle", caplog) # Drop last candle - data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", + data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC", fill_missing=False, drop_incomplete=True) assert len(data) == 3 diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py index 0318e5a82..1dbe20936 100644 --- a/tests/data/test_dataprovider.py +++ b/tests/data/test_dataprovider.py @@ -9,32 +9,32 @@ from tests.conftest import get_patched_exchange def test_ohlcv(mocker, default_conf, ticker_history): default_conf["runmode"] = RunMode.DRY_RUN - ticker_interval = default_conf["ticker_interval"] + timeframe = default_conf["ticker_interval"] exchange = get_patched_exchange(mocker, default_conf) - exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history - exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history + exchange._klines[("XRP/BTC", timeframe)] = ticker_history + exchange._klines[("UNITTEST/BTC", timeframe)] = ticker_history dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.DRY_RUN - assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval)) - assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) - assert dp.ohlcv("UNITTEST/BTC", ticker_interval) is not ticker_history - assert dp.ohlcv("UNITTEST/BTC", ticker_interval, copy=False) is ticker_history - assert not dp.ohlcv("UNITTEST/BTC", ticker_interval).empty - assert dp.ohlcv("NONESENSE/AAA", ticker_interval).empty + assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe)) + assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame) + assert dp.ohlcv("UNITTEST/BTC", timeframe) is not ticker_history + assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False) is ticker_history + assert not dp.ohlcv("UNITTEST/BTC", timeframe).empty + assert dp.ohlcv("NONESENSE/AAA", timeframe).empty # Test with and without parameter - assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC")) + assert dp.ohlcv("UNITTEST/BTC", timeframe).equals(dp.ohlcv("UNITTEST/BTC")) default_conf["runmode"] = RunMode.LIVE dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.LIVE - assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) + assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame) default_conf["runmode"] = RunMode.BACKTEST dp = DataProvider(default_conf, exchange) assert dp.runmode == RunMode.BACKTEST - assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty + assert dp.ohlcv("UNITTEST/BTC", timeframe).empty def test_historic_ohlcv(mocker, default_conf, ticker_history): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 68fac8632..a21a5f3ac 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1047,8 +1047,8 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name): ] pair = 'ETH/BTC' - async def mock_candle_hist(pair, ticker_interval, since_ms): - return pair, ticker_interval, tick + async def mock_candle_hist(pair, timeframe, since_ms): + return pair, timeframe, tick exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls From 334ac8b10ccbedd2910cf054b26c18f30e668d96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Nov 2019 20:34:06 +0100 Subject: [PATCH 108/157] Adapt documentation for timeframe --- docs/strategy-customization.md | 8 ++++---- docs/strategy_analysis_example.md | 4 ++-- user_data/notebooks/strategy_analysis_example.ipynb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 72938f9af..34f86f2ce 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -314,9 +314,9 @@ Please always check the mode of operation to select the correct method to get da #### Possible options for DataProvider - `available_pairs` - Property with tuples listing cached pairs with their intervals (pair, interval). -- `ohlcv(pair, ticker_interval)` - Currently cached ticker data for the pair, returns DataFrame or empty DataFrame. -- `historic_ohlcv(pair, ticker_interval)` - Returns historical data stored on disk. -- `get_pair_dataframe(pair, ticker_interval)` - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes). +- `ohlcv(pair, timeframe)` - Currently cached ticker data for the pair, returns DataFrame or empty DataFrame. +- `historic_ohlcv(pair, timeframe)` - Returns historical data stored on disk. +- `get_pair_dataframe(pair, timeframe)` - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes). - `orderbook(pair, maximum)` - Returns latest orderbook data for the pair, a dict with bids/asks with a total of `maximum` entries. - `market(pair)` - Returns market data for the pair: fees, limits, precisions, activity flag, etc. See [ccxt documentation](https://github.com/ccxt/ccxt/wiki/Manual#markets) for more details on Market data structure. - `runmode` - Property containing the current runmode. @@ -327,7 +327,7 @@ Please always check the mode of operation to select the correct method to get da if self.dp: inf_pair, inf_timeframe = self.informative_pairs()[0] informative = self.dp.get_pair_dataframe(pair=inf_pair, - ticker_interval=inf_timeframe) + timeframe=inf_timeframe) ``` !!! Warning "Warning about backtesting" diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index aa4578ca7..9e61bda65 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -10,7 +10,7 @@ from pathlib import Path # Customize these according to your needs. # Define some constants -ticker_interval = "5m" +timeframe = "5m" # Name of the strategy class strategy_name = 'SampleStrategy' # Path to user data @@ -29,7 +29,7 @@ pair = "BTC_USDT" from freqtrade.data.history import load_pair_history candles = load_pair_history(datadir=data_location, - ticker_interval=ticker_interval, + timeframe=timeframe, pair=pair) # Confirm success diff --git a/user_data/notebooks/strategy_analysis_example.ipynb b/user_data/notebooks/strategy_analysis_example.ipynb index 03dc83b4e..2876ea938 100644 --- a/user_data/notebooks/strategy_analysis_example.ipynb +++ b/user_data/notebooks/strategy_analysis_example.ipynb @@ -26,7 +26,7 @@ "# Customize these according to your needs.\n", "\n", "# Define some constants\n", - "ticker_interval = \"5m\"\n", + "timeframe = \"5m\"\n", "# Name of the strategy class\n", "strategy_name = 'SampleStrategy'\n", "# Path to user data\n", @@ -49,7 +49,7 @@ "from freqtrade.data.history import load_pair_history\n", "\n", "candles = load_pair_history(datadir=data_location,\n", - " ticker_interval=ticker_interval,\n", + " timeframe=timeframe,\n", " pair=pair)\n", "\n", "# Confirm success\n", From 1c57a4ac35435914b1a7330129185ee6dec55be1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Nov 2019 20:34:39 +0100 Subject: [PATCH 109/157] more replacements of ticker_interval --- freqtrade/constants.py | 4 ++-- freqtrade/data/btanalysis.py | 6 +++--- freqtrade/optimize/backtesting.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 5fdd45916..f34232bb1 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -24,7 +24,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList'] DRY_RUN_WALLET = 999.9 MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons -TICKER_INTERVALS = [ +TIMEFRAMES = [ '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', '1d', '3d', '1w', @@ -57,7 +57,7 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'max_open_trades': {'type': 'integer', 'minimum': -1}, - 'ticker_interval': {'type': 'string', 'enum': TICKER_INTERVALS}, + 'ticker_interval': {'type': 'string', 'enum': TIMEFRAMES}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']}, 'stake_amount': { "type": ["number", "string"], diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 2f7a234ce..379c80060 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -178,9 +178,9 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, :return: Returns df with one additional column, col_name, containing the cumulative profit. """ from freqtrade.exchange import timeframe_to_minutes - ticker_minutes = timeframe_to_minutes(timeframe) - # Resample to ticker_interval to make sure trades match candles - _trades_sum = trades.resample(f'{ticker_minutes}min', on='close_time')[['profitperc']].sum() + timeframe_minutes = timeframe_to_minutes(timeframe) + # Resample to timeframe to make sure trades match candles + _trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_time')[['profitperc']].sum() df.loc[:, col_name] = _trades_sum.cumsum() # Set first value to 0 df.loc[df.iloc[0].name, col_name] = 0 diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 79478076b..2c2d116a4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -121,7 +121,7 @@ class Backtesting: min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days ) # Adjust startts forward if not enough data is available - timerange.adjust_start_if_necessary(timeframe_to_seconds(self.ticker_interval), + timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe), self.required_startup, min_date) return data, timerange From c449e3928057213d14d2de2ab6c9f46ac51da5d5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 3 Nov 2019 10:01:05 +0100 Subject: [PATCH 110/157] Replace more occurances of ticker_interval --- freqtrade/configuration/timerange.py | 6 +++--- freqtrade/optimize/hyperopt_interface.py | 8 ++++---- tests/optimize/__init__.py | 4 ++-- tests/optimize/test_backtest_detail.py | 4 ++-- tests/optimize/test_backtesting.py | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py index 156f0e1e2..a8be873df 100644 --- a/freqtrade/configuration/timerange.py +++ b/freqtrade/configuration/timerange.py @@ -39,12 +39,12 @@ class TimeRange: if self.startts: self.startts = self.startts - seconds - def adjust_start_if_necessary(self, ticker_interval_secs: int, startup_candles: int, + def adjust_start_if_necessary(self, timeframe_secs: int, startup_candles: int, min_date: arrow.Arrow) -> None: """ Adjust startts by candles. Applies only if no startup-candles have been available. - :param ticker_interval_secs: Ticker interval in seconds e.g. `timeframe_to_seconds('5m')` + :param timeframe_secs: Ticker timeframe in seconds e.g. `timeframe_to_seconds('5m')` :param startup_candles: Number of candles to move start-date forward :param min_date: Minimum data date loaded. Key kriterium to decide if start-time has to be moved @@ -55,7 +55,7 @@ class TimeRange: # If no startts was defined, or backtest-data starts at the defined backtest-date logger.warning("Moving start-date by %s candles to account for startup time.", startup_candles) - self.startts = (min_date.timestamp + ticker_interval_secs * startup_candles) + self.startts = (min_date.timestamp + timeframe_secs * startup_candles) self.starttype = 'date' @staticmethod diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 142f305df..ac41ba92f 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -106,10 +106,10 @@ class IHyperOpt(ABC): roi_t_alpha = 1.0 roi_p_alpha = 1.0 - ticker_interval_mins = timeframe_to_minutes(IHyperOpt.ticker_interval) + timeframe_mins = timeframe_to_minutes(IHyperOpt.ticker_interval) # We define here limits for the ROI space parameters automagically adapted to the - # ticker_interval used by the bot: + # timeframe used by the bot: # # * 'roi_t' (limits for the time intervals in the ROI tables) components # are scaled linearly. @@ -117,8 +117,8 @@ class IHyperOpt(ABC): # # The scaling is designed so that it maps exactly to the legacy Freqtrade roi_space() # method for the 5m ticker interval. - roi_t_scale = ticker_interval_mins / 5 - roi_p_scale = math.log1p(ticker_interval_mins) / math.log1p(5) + roi_t_scale = timeframe_mins / 5 + roi_p_scale = math.log1p(timeframe_mins) / math.log1p(5) roi_limits = { 'roi_t1_min': int(10 * roi_t_scale * roi_t_alpha), 'roi_t1_max': int(120 * roi_t_scale * roi_t_alpha), diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index fdbaaa54d..8756143a0 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -7,7 +7,7 @@ from freqtrade.exchange import timeframe_to_minutes from freqtrade.strategy.interface import SellType ticker_start_time = arrow.get(2018, 10, 3) -tests_ticker_interval = '1h' +tests_timeframe = '1h' class BTrade(NamedTuple): @@ -36,7 +36,7 @@ class BTContainer(NamedTuple): def _get_frame_time_from_offset(offset): - return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval)) + return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_timeframe)) ).datetime diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 54f4c8796..3f6cc8c9a 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -9,7 +9,7 @@ from freqtrade.optimize.backtesting import Backtesting from freqtrade.strategy.interface import SellType from tests.conftest import patch_exchange from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, - _get_frame_time_from_offset, tests_ticker_interval) + _get_frame_time_from_offset, tests_timeframe) # Test 0: Sell with signal sell in candle 3 # Test with Stop-loss at 1% @@ -293,7 +293,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: """ default_conf["stoploss"] = data.stop_loss default_conf["minimal_roi"] = data.roi - default_conf["ticker_interval"] = tests_ticker_interval + default_conf["ticker_interval"] = tests_timeframe default_conf["trailing_stop"] = data.trailing_stop default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached # Only add this to configuration If it's necessary diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index a5ab6d84c..508c12e89 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -307,7 +307,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) backtesting = Backtesting(default_conf) assert backtesting.config == default_conf - assert backtesting.ticker_interval == '5m' + assert backtesting.timeframe == '5m' assert callable(backtesting.strategy.tickerdata_to_dataframe) assert callable(backtesting.strategy.advise_buy) assert callable(backtesting.strategy.advise_sell) From 2eb651325108c9602601c7cd37538ab4886074d5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 15:43:10 +0100 Subject: [PATCH 111/157] Improve timedout handling --- freqtrade/freqtradebot.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7e9706803..512fc4061 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -139,10 +139,9 @@ class FreqtradeBot: if len(trades) < self.config['max_open_trades']: self.process_maybe_execute_buys() - if 'unfilledtimeout' in self.config: - # Check and handle any timed out open orders - self.check_handle_timedout() - Trade.session.flush() + # Check and handle any timed out open orders + self.check_handle_timedout() + Trade.session.flush() if (self.heartbeat_interval and (arrow.utcnow().timestamp - self._heartbeat_msg > self.heartbeat_interval)): @@ -756,23 +755,28 @@ class FreqtradeBot: return True return False + def _check_timed_out(self, side: str, order: dict) -> bool: + """ + Check if timeout is active, and if the order is still open and timed out + """ + timeout = self.config.get('unfilledtimeout', {}).get(side) + ordertime = arrow.get(order['datetime']).datetime + if timeout: + timeout_threshold = arrow.utcnow().shift(minutes=-timeout).datetime + + return (order['status'] == 'open' and order['side'] == side + and ordertime < timeout_threshold) + return False + def check_handle_timedout(self) -> None: """ Check if any orders are timed out and cancel if neccessary :param timeoutvalue: Number of minutes until order is considered timed out :return: None """ - buy_timeout = self.config['unfilledtimeout']['buy'] - sell_timeout = self.config['unfilledtimeout']['sell'] - buy_timeout_threshold = arrow.utcnow().shift(minutes=-buy_timeout).datetime - sell_timeout_threshold = arrow.utcnow().shift(minutes=-sell_timeout).datetime for trade in Trade.get_open_order_trades(): try: - # FIXME: Somehow the query above returns results - # where the open_order_id is in fact None. - # This is probably because the record got - # updated via /forcesell in a different thread. if not trade.open_order_id: continue order = self.exchange.get_order(trade.open_order_id, trade.pair) @@ -782,7 +786,6 @@ class FreqtradeBot: trade, traceback.format_exc()) continue - ordertime = arrow.get(order['datetime']).datetime # Check if trade is still actually open if float(order['remaining']) == 0.0: @@ -790,15 +793,13 @@ class FreqtradeBot: continue if ((order['side'] == 'buy' and order['status'] == 'canceled') - or (order['status'] == 'open' - and order['side'] == 'buy' and ordertime < buy_timeout_threshold)): + or (self._check_timed_out('buy', order))): self.handle_timedout_limit_buy(trade, order) self.wallets.update() elif ((order['side'] == 'sell' and order['status'] == 'canceled') - or (order['status'] == 'open' - and order['side'] == 'sell' and ordertime < sell_timeout_threshold)): + or (self._check_timed_out('sell', order))): self.handle_timedout_limit_sell(trade, order) self.wallets.update() From 5b62ad876e0792cdd98909ef6904d9ef5bcd6376 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 09:38:06 +0100 Subject: [PATCH 112/157] Remove hyperopts occurances --- .travis.yml | 2 +- docs/bot-usage.md | 4 ++-- freqtrade/configuration/cli_options.py | 4 ++-- freqtrade/optimize/__init__.py | 2 +- freqtrade/optimize/hyperopt_interface.py | 6 +++--- freqtrade/optimize/hyperopt_loss_interface.py | 4 ++-- freqtrade/resolvers/hyperopt_resolver.py | 2 +- freqtrade/utils.py | 2 +- user_data/hyperopts/sample_hyperopt.py | 2 +- user_data/hyperopts/sample_hyperopt_advanced.py | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index a5a8093ec..8aaff553f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: name: backtest - script: - cp config.json.example config.json - - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpts + - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt name: hyperopt - script: flake8 name: flake8 diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 8c85965a4..b88e33bd5 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -131,7 +131,7 @@ You can add the entry "user_data_dir" setting to your configuration, to always p Alternatively, pass in `--userdir` to every command. The bot will fail to start if the directory does not exist, but will create necessary subdirectories. -This directory should contain your custom strategies, custom hyperopts and hyperopt loss functions, backtesting historical data (downloaded using either backtesting command or the download script) and plot outputs. +This directory should contain your custom strategies, custom hyperopt and hyperopt loss functions, backtesting historical data (downloaded using either backtesting command or the download script) and plot outputs. It is recommended to use version control to keep track of changes to your strategies. @@ -294,7 +294,7 @@ optional arguments: entry and exit). --hyperopt NAME Specify hyperopt class name which will be used by the bot. - --hyperopt-path PATH Specify additional lookup path for Hyperopts and + --hyperopt-path PATH Specify additional lookup path for Hyperopt and Hyperopt Loss functions. --eps, --enable-position-stacking Allow buying the same pair multiple times (position diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index ff2178108..6dc5ef026 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -166,7 +166,7 @@ AVAILABLE_CLI_OPTIONS = { ), "hyperopt_path": Arg( '--hyperopt-path', - help='Specify additional lookup path for Hyperopts and Hyperopt Loss functions.', + help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.', metavar='PATH', ), "epochs": Arg( @@ -239,7 +239,7 @@ AVAILABLE_CLI_OPTIONS = { help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' 'Different functions can generate completely different results, ' 'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' - 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss ' + 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.' '(default: `%(default)s`).', metavar='NAME', default=constants.DEFAULT_HYPEROPT_LOSS, diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 3adf5eb43..1f2f588ef 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -78,7 +78,7 @@ def start_hyperopt(args: Dict[str, Any]) -> None: except Timeout: logger.info("Another running instance of freqtrade Hyperopt detected.") logger.info("Simultaneous execution of multiple Hyperopt commands is not supported. " - "Hyperopt module is resource hungry. Please run your Hyperopts sequentially " + "Hyperopt module is resource hungry. Please run your Hyperopt sequentially " "or on separate machines.") logger.info("Quitting now.") # TODO: return False here in order to help freqtrade to exit diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 142f305df..5cfd98632 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -1,6 +1,6 @@ """ IHyperOpt interface -This module defines the interface to apply for hyperopts +This module defines the interface to apply for hyperopt """ import logging import math @@ -27,8 +27,8 @@ def _format_exception_message(method: str, space: str) -> str: class IHyperOpt(ABC): """ - Interface for freqtrade hyperopts - Defines the mandatory structure must follow any custom hyperopts + Interface for freqtrade hyperopt + Defines the mandatory structure must follow any custom hyperopt Class attributes you can use: ticker_interval -> int: value of the ticker interval to use for the strategy diff --git a/freqtrade/optimize/hyperopt_loss_interface.py b/freqtrade/optimize/hyperopt_loss_interface.py index b11b6e661..879a9f0e9 100644 --- a/freqtrade/optimize/hyperopt_loss_interface.py +++ b/freqtrade/optimize/hyperopt_loss_interface.py @@ -1,6 +1,6 @@ """ IHyperOptLoss interface -This module defines the interface for the loss-function for hyperopts +This module defines the interface for the loss-function for hyperopt """ from abc import ABC, abstractmethod @@ -11,7 +11,7 @@ from pandas import DataFrame class IHyperOptLoss(ABC): """ - Interface for freqtrade hyperopts Loss functions. + Interface for freqtrade hyperopt Loss functions. Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.) """ ticker_interval: str diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 72816a9ce..df1ff182c 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -1,7 +1,7 @@ # pragma pylint: disable=attribute-defined-outside-init """ -This module load custom hyperopts +This module load custom hyperopt """ import logging from pathlib import Path diff --git a/freqtrade/utils.py b/freqtrade/utils.py index ce1f8a7c5..ee7e3296f 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -70,7 +70,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: def start_create_userdir(args: Dict[str, Any]) -> None: """ - Create "user_data" directory to contain user data strategies, hyperopts, ...) + Create "user_data" directory to contain user data strategies, hyperopt, ...) :param args: Cli args from Arguments() :return: None """ diff --git a/user_data/hyperopts/sample_hyperopt.py b/user_data/hyperopts/sample_hyperopt.py index 2721ab405..3be05f121 100644 --- a/user_data/hyperopts/sample_hyperopt.py +++ b/user_data/hyperopts/sample_hyperopt.py @@ -12,7 +12,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.optimize.hyperopt_interface import IHyperOpt -class SampleHyperOpts(IHyperOpt): +class SampleHyperOpt(IHyperOpt): """ This is a sample Hyperopt to inspire you. Feel free to customize it. diff --git a/user_data/hyperopts/sample_hyperopt_advanced.py b/user_data/hyperopts/sample_hyperopt_advanced.py index c5d28878c..66182edcf 100644 --- a/user_data/hyperopts/sample_hyperopt_advanced.py +++ b/user_data/hyperopts/sample_hyperopt_advanced.py @@ -14,7 +14,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.optimize.hyperopt_interface import IHyperOpt -class AdvancedSampleHyperOpts(IHyperOpt): +class AdvancedSampleHyperOpt(IHyperOpt): """ This is a sample hyperopt to inspire you. Feel free to customize it. From c42c5a1f85fb38eeefa72a2b0df2da0b18fd25a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 10:03:59 +0100 Subject: [PATCH 113/157] Adjust "requires subcommand" message --- freqtrade/main.py | 9 ++++++--- tests/test_main.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index d984ff487..0a2adf71a 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -38,9 +38,12 @@ def main(sysargv: List[str] = None) -> None: else: # No subcommand was issued. raise OperationalException( - "Usage of freqtrade requires a subcommand.\n" - "To use the previous behaviour, run freqtrade with `freqtrade trade [...]`.\n" - "To see a full list of options, please use `freqtrade --help`" + "Usage of Freqtrade requires a subcommand to be specified.\n" + "To have the previous behavior (bot executing trades in live/dry-run modes, " + "depending on the value of the `dry_run` setting in the config), run freqtrade " + "as `freqtrade trade [options...]`.\n" + "To see the full list of options available, please use " + "`freqtrade --help` or `freqtrade --help`." ) except SystemExit as e: diff --git a/tests/test_main.py b/tests/test_main.py index dac960886..4e97c375d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -18,7 +18,7 @@ from tests.conftest import (log_has, log_has_re, patch_exchange, def test_parse_args_None(caplog) -> None: with pytest.raises(SystemExit): main([]) - assert log_has_re(r"Usage of freqtrade requires a subcommand\.", caplog) + assert log_has_re(r"Usage of Freqtrade requires a subcommand.*", caplog) def test_parse_args_backtesting(mocker) -> None: From 66619204bacefeb3f42d52a6d8f1fbd50cea5a8a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 11:13:48 +0100 Subject: [PATCH 114/157] re-add hyperopts multiple ... --- docs/bot-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index b88e33bd5..4665878d4 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -131,7 +131,7 @@ You can add the entry "user_data_dir" setting to your configuration, to always p Alternatively, pass in `--userdir` to every command. The bot will fail to start if the directory does not exist, but will create necessary subdirectories. -This directory should contain your custom strategies, custom hyperopt and hyperopt loss functions, backtesting historical data (downloaded using either backtesting command or the download script) and plot outputs. +This directory should contain your custom strategies, custom hyperopts and hyperopt loss functions, backtesting historical data (downloaded using either backtesting command or the download script) and plot outputs. It is recommended to use version control to keep track of changes to your strategies. From 6ac73f7cde81ac04044a956138a1a627991fd1e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 11:28:26 +0100 Subject: [PATCH 115/157] Update missed strings --- freqtrade/data/dataprovider.py | 4 ++-- freqtrade/data/history.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index db71ff029..7b7159145 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -61,7 +61,7 @@ class DataProvider: """ Get stored historic ohlcv data :param pair: pair to get the data for - :param timeframe: ticker interval to get data for + :param timeframe: timeframe to get data for """ return load_pair_history(pair=pair, timeframe=timeframe or self._config['ticker_interval'], @@ -73,7 +73,7 @@ class DataProvider: Return pair ohlcv data, either live or cached historical -- depending on the runmode. :param pair: pair to get the data for - :param timeframe: ticker interval to get data for + :param timeframe: timeframe to get data for :return: Dataframe for this pair """ if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index 3dea41c55..d45b1c890 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -279,7 +279,7 @@ def download_pair_history(datadir: Path, timeframe: str = '5m', timerange: Optional[TimeRange] = None) -> bool: """ - Download the latest ticker intervals from the exchange for the pair passed in parameters + Download latest candles from the exchange for the pair and timeframe passed in parameters The data is downloaded starting from the last correct data that exists in a cache. If timerange starts earlier than the data in the cache, the full data will be redownloaded From 62c1ff776e6faeda8aa6e1acc4acecaf60c84428 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 13:59:38 +0100 Subject: [PATCH 116/157] update action to 2.1.0 --- .github/workflows/docker_update_readme.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index bc063617a..634517dc9 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@master - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v2.0.0 + uses: peter-evans/dockerhub-description@v2.1.0 env: DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} From e26bbc7de8e7d7d876a434f16c91d4e60ead393e Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 13 Nov 2019 19:50:54 +0300 Subject: [PATCH 117/157] Add fix for bibox exchange --- freqtrade/exchange/__init__.py | 1 + freqtrade/exchange/bibox.py | 21 +++++++++++++++++++++ freqtrade/exchange/exchange.py | 7 +++++++ 3 files changed, 29 insertions(+) create mode 100644 freqtrade/exchange/bibox.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index c107f7abc..df18bca02 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -15,3 +15,4 @@ from freqtrade.exchange.exchange import (market_is_active, # noqa: F401 symbol_is_pair) from freqtrade.exchange.kraken import Kraken # noqa: F401 from freqtrade.exchange.binance import Binance # noqa: F401 +from freqtrade.exchange.bibox import Bibox # noqa: F401 diff --git a/freqtrade/exchange/bibox.py b/freqtrade/exchange/bibox.py new file mode 100644 index 000000000..1f042c221 --- /dev/null +++ b/freqtrade/exchange/bibox.py @@ -0,0 +1,21 @@ +""" Bibox exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + +logger = logging.getLogger(__name__) + + +class Bibox(Exchange): + """ + Bibox exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + + Please note that this exchange is not included in the list of exchanges + officially supported by the Freqtrade development team. So some features + may still not work as expected. + """ + + # Adjust ccxt exchange API metadata info + _ccxt_has: Dict = {"fetchCurrencies": False} diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 05db45c9b..0dd8c4ff2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -30,6 +30,9 @@ class Exchange: _config: Dict = {} + # Adjustments to ccxt exchange API metadata info (ccxt exchange `has` options) + _ccxt_has: Dict = {} + # Parameters to add directly to buy/sell calls (like agreeing to trading agreement) _params: Dict = {} @@ -152,6 +155,10 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(f"Initialization of ccxt failed. Reason: {e}") from e + # Adjust ccxt API metadata info (`has` options) for the exchange + for k, v in self._ccxt_has.items(): + api.has[k] = v + self.set_sandbox(api, exchange_config, name) return api From 6174a5dd55aed41764de24e345e9848312879c56 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 13 Nov 2019 20:22:23 +0300 Subject: [PATCH 118/157] Reimplement adjustment of ccxt 'has' with more generic ccxt_config class attribute --- freqtrade/exchange/bibox.py | 5 +++-- freqtrade/exchange/exchange.py | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/bibox.py b/freqtrade/exchange/bibox.py index 1f042c221..229abe766 100644 --- a/freqtrade/exchange/bibox.py +++ b/freqtrade/exchange/bibox.py @@ -17,5 +17,6 @@ class Bibox(Exchange): may still not work as expected. """ - # Adjust ccxt exchange API metadata info - _ccxt_has: Dict = {"fetchCurrencies": False} + # fetchCurrencies API point requires authentication for Bibox, + # so switch it off for Freqtrade load_markets() + _ccxt_config: Dict = {"has": {"fetchCurrencies": False}} diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 0dd8c4ff2..30868df07 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -30,8 +30,8 @@ class Exchange: _config: Dict = {} - # Adjustments to ccxt exchange API metadata info (ccxt exchange `has` options) - _ccxt_has: Dict = {} + # Parameters to add directly to ccxt sync/async initialization. + _ccxt_config: Dict = {} # Parameters to add directly to buy/sell calls (like agreeing to trading agreement) _params: Dict = {} @@ -94,10 +94,17 @@ class Exchange: self._trades_pagination_arg = self._ft_has['trades_pagination_arg'] # Initialize ccxt objects + ccxt_config = self._ccxt_config.copy() + ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), + ccxt_config) self._api = self._init_ccxt( - exchange_config, ccxt_kwargs=exchange_config.get('ccxt_config')) + exchange_config, ccxt_kwargs=ccxt_config) + + ccxt_async_config = self._ccxt_config.copy() + ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}), + ccxt_async_config) self._api_async = self._init_ccxt( - exchange_config, ccxt_async, ccxt_kwargs=exchange_config.get('ccxt_async_config')) + exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) logger.info('Using Exchange "%s"', self.name) @@ -155,10 +162,6 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(f"Initialization of ccxt failed. Reason: {e}") from e - # Adjust ccxt API metadata info (`has` options) for the exchange - for k, v in self._ccxt_has.items(): - api.has[k] = v - self.set_sandbox(api, exchange_config, name) return api From 68904296e7db5a006b8915bd273fc09d509c0c27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 19:38:38 +0100 Subject: [PATCH 119/157] Allow timeout of 0 --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 512fc4061..f7cec080b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -761,7 +761,7 @@ class FreqtradeBot: """ timeout = self.config.get('unfilledtimeout', {}).get(side) ordertime = arrow.get(order['datetime']).datetime - if timeout: + if timeout is not None: timeout_threshold = arrow.utcnow().shift(minutes=-timeout).datetime return (order['status'] == 'open' and order['side'] == side From c8c48156dd7114b88780a0a119e46a443e356222 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 20:44:55 +0100 Subject: [PATCH 120/157] Don't load trades twice ... --- freqtrade/data/history.py | 10 +++++++--- freqtrade/plot/plotting.py | 7 ++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/freqtrade/data/history.py b/freqtrade/data/history.py index d45b1c890..ec95be874 100644 --- a/freqtrade/data/history.py +++ b/freqtrade/data/history.py @@ -50,16 +50,20 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]: return tickerlist[start_index:stop_index] -def trim_dataframe(df: DataFrame, timerange: TimeRange) -> DataFrame: +def trim_dataframe(df: DataFrame, timerange: TimeRange, df_date_col: str = 'date') -> DataFrame: """ Trim dataframe based on given timerange + :param df: Dataframe to trim + :param timerange: timerange (use start and end date if available) + :param: df_date_col: Column in the dataframe to use as Date column + :return: trimmed dataframe """ if timerange.starttype == 'date': start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) - df = df.loc[df['date'] >= start, :] + df = df.loc[df[df_date_col] >= start, :] if timerange.stoptype == 'date': stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) - df = df.loc[df['date'] <= stop, :] + df = df.loc[df[df_date_col] <= stop, :] return df diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 01396aea9..6f78802ba 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -47,7 +47,7 @@ def init_plotscript(config): db_url=config.get('db_url'), exportfilename=config.get('exportfilename'), ) - + trades = history.trim_dataframe(trades, timerange, 'open_time') return {"tickers": tickers, "trades": trades, "pairs": pairs, @@ -377,10 +377,7 @@ def plot_profit(config: Dict[str, Any]) -> None: in helping out to find a good algorithm. """ plot_elements = init_plotscript(config) - trades = load_trades(config['trade_source'], - db_url=str(config.get('db_url')), - exportfilename=str(config.get('exportfilename')), - ) + trades = plot_elements['trades'] # Filter trades to relevant pairs trades = trades[trades['pair'].isin(plot_elements["pairs"])] # Create an average close price of all the pairs that were involved. From 38243c52fd2133f83299efc7bf94c2703b978209 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Nov 2019 20:45:16 +0100 Subject: [PATCH 121/157] Filter open trades - they are not added to the profit calc --- freqtrade/plot/plotting.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 6f78802ba..57a02dd6b 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -379,7 +379,12 @@ def plot_profit(config: Dict[str, Any]) -> None: plot_elements = init_plotscript(config) trades = plot_elements['trades'] # Filter trades to relevant pairs - trades = trades[trades['pair'].isin(plot_elements["pairs"])] + # Remove open pairs - we don't know the profit yet so can't calculate profit for these. + # Also, If only one open pair is left, then the profit-generation would fail. + trades = trades[(trades['pair'].isin(plot_elements["pairs"])) + & (~trades['close_time'].isnull()) + ] + # Create an average close price of all the pairs that were involved. # this could be useful to gauge the overall market trend fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"], From 569a547b3f6dac3122d884bee03065371e127b1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2019 06:49:21 +0100 Subject: [PATCH 122/157] Update Actions CI to new subcommands --- .github/workflows/ci.yml | 8 ++++---- Dockerfile.pi | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67862282a..c1112636a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,12 +72,12 @@ jobs: - name: Backtesting run: | cp config.json.example config.json - freqtrade --datadir tests/testdata backtesting + freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy - name: Hyperopt run: | cp config.json.example config.json - freqtrade --datadir tests/testdata --strategy SampleStrategy hyperopt --customhyperopt SampleHyperOpts -e 5 + freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpts - name: Flake8 run: | @@ -137,12 +137,12 @@ jobs: - name: Backtesting run: | cp config.json.example config.json - freqtrade --datadir tests/testdata backtesting + freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy - name: Hyperopt run: | cp config.json.example config.json - freqtrade --datadir tests/testdata --strategy SampleStrategy hyperopt --customhyperopt SampleHyperOpts -e 5 + freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --customhyperopt SampleHyperOpts - name: Flake8 run: | diff --git a/Dockerfile.pi b/Dockerfile.pi index 85ba5892f..279f85a04 100644 --- a/Dockerfile.pi +++ b/Dockerfile.pi @@ -38,3 +38,4 @@ RUN ~/berryconda3/bin/pip install -e . --no-cache-dir RUN [ "cross-build-end" ] ENTRYPOINT ["/root/berryconda3/bin/python","./freqtrade/main.py"] +CMD [ "trade" ] From f94d46316e68c94036567d8fd55cecb961f3f45d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2019 06:51:02 +0100 Subject: [PATCH 123/157] update checkout action to pinned version --- .github/workflows/docker_update_readme.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index 634517dc9..57a7e591e 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -8,7 +8,7 @@ jobs: dockerHubDescription: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v1 - name: Docker Hub Description uses: peter-evans/dockerhub-description@v2.1.0 env: From 3b9899dfd424f8f7741e8cf468a69af3c5ae8899 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2019 07:06:00 +0100 Subject: [PATCH 124/157] hyperopts ... --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1112636a..22931eeaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: - name: Hyperopt run: | cp config.json.example config.json - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpts + freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt - name: Flake8 run: | @@ -142,7 +142,7 @@ jobs: - name: Hyperopt run: | cp config.json.example config.json - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --customhyperopt SampleHyperOpts + freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --customhyperopt SampleHyperOpt - name: Flake8 run: | From b167fb071a68f5d3df6b9d9258cc667a7cc8da25 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2019 08:44:10 +0100 Subject: [PATCH 125/157] fix windows test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22931eeaa..04e52c0fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,7 +142,7 @@ jobs: - name: Hyperopt run: | cp config.json.example config.json - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --customhyperopt SampleHyperOpt + freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt - name: Flake8 run: | From edc0d7f2c74d564b0468f27e9eb53e438284dd6a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 15 Nov 2019 20:10:17 +0100 Subject: [PATCH 126/157] Fix non-terminating bot --- freqtrade/main.py | 3 --- freqtrade/utils.py | 11 +++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 0a2adf71a..7afaeb1a2 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -27,7 +27,6 @@ def main(sysargv: List[str] = None) -> None: """ return_code: Any = 1 - worker = None try: arguments = Arguments(sysargv) args = arguments.get_parsed_arg() @@ -57,8 +56,6 @@ def main(sysargv: List[str] = None) -> None: except Exception: logger.exception('Fatal exception!') finally: - if worker: - worker.exit() sys.exit(return_code) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 5ad134ef9..ff54790a5 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -45,8 +45,15 @@ def start_trading(args: Dict[str, Any]) -> int: """ from freqtrade.worker import Worker # Load and run worker - worker = Worker(args) - worker.run() + try: + worker = Worker(args) + worker.run() + except KeyboardInterrupt: + logger.info('SIGINT received, aborting ...') + finally: + if worker: + logger.info("worker found ... calling exit") + worker.exit() return 0 From 6e0655b3b70adf5be4af47e8a508a8bde74b3002 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Nov 2019 09:47:35 +0100 Subject: [PATCH 127/157] add empty worker variable --- freqtrade/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index ff54790a5..b8ab7504e 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -45,6 +45,7 @@ def start_trading(args: Dict[str, Any]) -> int: """ from freqtrade.worker import Worker # Load and run worker + worker = None try: worker = Worker(args) worker.run() From 91047830fd282baa72a21f0425f7e904e92ddd33 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Nov 2019 09:56:16 +0100 Subject: [PATCH 128/157] Add tst for worker termination --- tests/test_utils.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 3dba7df1b..bbb4fc648 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -8,7 +8,8 @@ from freqtrade import OperationalException from freqtrade.state import RunMode from freqtrade.utils import (setup_utils_configuration, start_create_userdir, start_download_data, start_list_exchanges, - start_list_markets, start_list_timeframes) + start_list_markets, start_list_timeframes, + start_trading) from tests.conftest import get_args, log_has, patch_exchange @@ -24,6 +25,29 @@ def test_setup_utils_configuration(): assert config['exchange']['secret'] == '' +def test_start_trading_fail(mocker): + + mocker.patch("freqtrade.worker.Worker.run", MagicMock(side_effect=OperationalException)) + + mocker.patch("freqtrade.worker.Worker.__init__", MagicMock(return_value=None)) + + exitmock = mocker.patch("freqtrade.worker.Worker.exit", MagicMock()) + args = [ + 'trade', + '-c', 'config.json.example' + ] + with pytest.raises(OperationalException): + start_trading(get_args(args)) + assert exitmock.call_count == 1 + + exitmock.reset_mock() + + mocker.patch("freqtrade.worker.Worker.__init__", MagicMock(side_effect=OperationalException)) + with pytest.raises(OperationalException): + start_trading(get_args(args)) + assert exitmock.call_count == 0 + + def test_list_exchanges(capsys): args = [ From be53c0885df7ad29a6202502c47894c8ae3117c8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Nov 2019 10:49:32 +0100 Subject: [PATCH 129/157] Try moving coveralls to github actions --- .github/workflows/ci.yml | 6 ++++++ .travis.yml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04e52c0fd..12aff89f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,9 +64,15 @@ jobs: pip install -e . - name: Tests + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + COVERALLS_SERVICE_NAME: travis-ci + TRAVIS: "true" run: | pytest --random-order --cov=freqtrade --cov-config=.coveragerc # Allow failure for coveralls + # Fake travis environment to get coveralls working correctly + export TRAVIS_PULL_REQUEST="https://github.com/${GITHUB_REPOSITORY}/pull/$(cat $GITHUB_EVENT_PATH | jq -r .number)" coveralls || true - name: Backtesting diff --git a/.travis.yml b/.travis.yml index 80e4080d3..6073e1cce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ jobs: script: - pytest --random-order --cov=freqtrade --cov-config=.coveragerc # Allow failure for coveralls - - coveralls || true + # - coveralls || true name: pytest - script: - cp config.json.example config.json From b6a12044bafa2e1fc9adc75b3fb8173313bf8091 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 10:36:32 +0100 Subject: [PATCH 130/157] seperate docs job --- .github/workflows/ci.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12aff89f8..f8932cf07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,10 +93,6 @@ jobs: run: | mypy freqtrade scripts - - name: Documentation syntax - run: | - ./tests/test_docs.sh - - name: Slack Notification uses: homoluctus/slatify@v1.8.0 if: always() @@ -169,8 +165,26 @@ jobs: channel: '#notifications' url: ${{ secrets.SLACK_WEBHOOK }} + docs_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: Documentation syntax + run: | + ./tests/test_docs.sh + + - name: Slack Notification + uses: homoluctus/slatify@v1.8.0 + if: failure() + with: + type: ${{ job.status }} + job_name: '*Freqtrade Docs*' + channel: '#notifications' + url: ${{ secrets.SLACK_WEBHOOK }} + deploy: - needs: [ build, build_windows ] + needs: [ build, build_windows, docs_check ] runs-on: ubuntu-18.04 if: github.event_name == 'push' || github.event_name == 'schedule' steps: From 3aee8d2b2a347d2366088119e13c65c7b656f1ff Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 14:40:59 +0100 Subject: [PATCH 131/157] Improve rest api client / status response --- freqtrade/rpc/api_server.py | 7 +++++-- scripts/rest_client.py | 20 ++++++++++++++------ tests/rpc/test_rpc_apiserver.py | 4 ++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 3b59c9592..851806ec2 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -330,8 +330,11 @@ class ApiServer(RPC): Returns the current status of the trades in json format """ - results = self._rpc_trade_status() - return self.rest_dump(results) + try: + results = self._rpc_trade_status() + return self.rest_dump(results) + except RPCException: + return self.rest_dump([]) @require_login @rpc_catch_errors diff --git a/scripts/rest_client.py b/scripts/rest_client.py index a46b3ebfb..096286013 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -8,12 +8,14 @@ so it can be used as a standalone script. """ import argparse +import inspect import json import logging -import inspect -from urllib.parse import urlencode, urlparse, urlunparse +import sys from pathlib import Path +from urllib.parse import urlencode, urlparse, urlunparse +import rapidjson import requests from requests.exceptions import ConnectionError @@ -190,7 +192,9 @@ class FtRestClient(): def add_arguments(): parser = argparse.ArgumentParser() parser.add_argument("command", - help="Positional argument defining the command to execute.") + help="Positional argument defining the command to execute.", + nargs="?" + ) parser.add_argument('--show', help='Show possible methods with this client', @@ -221,9 +225,12 @@ def load_config(configfile): file = Path(configfile) if file.is_file(): with file.open("r") as f: - config = json.load(f) + config = rapidjson.load(f, parse_mode=rapidjson.PM_COMMENTS | + rapidjson.PM_TRAILING_COMMAS) return config - return {} + else: + logger.warning(f"Could not load config file {file}.") + sys.exit(1) def print_commands(): @@ -237,8 +244,9 @@ def print_commands(): def main(args): - if args.get("help"): + if args.get("show"): print_commands() + sys.exit() config = load_config(args["config"]) url = config.get("api_server", {}).get("server_url", "127.0.0.1") diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 6e65cf934..cbca7e3d5 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -417,8 +417,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets): ) rc = client_get(client, f"{BASE_URI}/status") - assert_response(rc, 502) - assert rc.json == {'error': 'Error querying _status: no active trade'} + assert_response(rc, 200) + assert rc.json == [] ftbot.create_trades() rc = client_get(client, f"{BASE_URI}/status") From 2c976bdd24b00ee448463d3399cc95d9e183d115 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 14:56:08 +0100 Subject: [PATCH 132/157] Add show_config endpoint --- freqtrade/rpc/api_server.py | 10 ++++++++++ freqtrade/rpc/rpc.py | 20 ++++++++++++++++++++ scripts/rest_client.py | 7 +++++++ tests/rpc/test_rpc_apiserver.py | 12 ++++++++++++ 4 files changed, 49 insertions(+) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 3b59c9592..0426eb598 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -169,6 +169,8 @@ class ApiServer(RPC): view_func=self._status, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/version', 'version', view_func=self._version, methods=['GET']) + self.app.add_url_rule(f'{BASE_URI}/show_config', 'show_config', + view_func=self._show_config, methods=['GET']) self.app.add_url_rule(f'{BASE_URI}/ping', 'ping', view_func=self._ping, methods=['GET']) @@ -241,6 +243,14 @@ class ApiServer(RPC): """ return self.rest_dump({"version": __version__}) + @require_login + @rpc_catch_errors + def _show_config(self): + """ + Prints the bot's version + """ + return self.rest_dump(self._rpc_show_config()) + @require_login @rpc_catch_errors def _reload_conf(self): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 5ab92cf33..c78951a6d 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -80,6 +80,26 @@ class RPC: def send_msg(self, msg: Dict[str, str]) -> None: """ Sends a message to all registered rpc modules """ + def _rpc_show_config(self) -> Dict: + """ + Return a dict of config options. + Explicitly does NOT return the full config to avoid leakage of sensitive + information via rpc. + """ + config = self._freqtrade.config + val = { + 'dry_run': config.get('dry_run', False), + 'stake_currency': config['stake_currency'], + 'stake_amount': config['stake_amount'], + 'minimal_roi': config['minimal_roi'].copy(), + 'stoploss': config['stoploss'], + 'trailing_stop': config['trailing_stop'], + 'ticker_interval': config['ticker_interval'], + 'exchange': config['exchange']['name'], + 'strategy': config['strategy'], + } + return val + def _rpc_trade_status(self) -> List[Dict[str, Any]]: """ Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is diff --git a/scripts/rest_client.py b/scripts/rest_client.py index a46b3ebfb..7408acab8 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -147,6 +147,13 @@ class FtRestClient(): """ return self._get("version") + def show_config(self): + """ + Returns part of the configuration, relevant for trading operations. + :return: json object containing the version + """ + return self._get("show_config") + def whitelist(self): """ Show the current whitelist diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 6e65cf934..d1ebe961b 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -284,6 +284,18 @@ def test_api_count(botclient, mocker, ticker, fee, markets): assert rc.json["max"] == 1.0 +def test_api_show_config(botclient, mocker): + ftbot, client = botclient + patch_get_signal(ftbot, (True, False)) + + rc = client_get(client, f"{BASE_URI}/show_config") + assert_response(rc) + assert 'dry_run' in rc.json + assert rc.json['exchange'] == 'bittrex' + assert rc.json['ticker_interval'] == '5m' + assert not rc.json['trailing_stop'] + + def test_api_daily(botclient, mocker, ticker, fee, markets): ftbot, client = botclient patch_get_signal(ftbot, (True, False)) From acab56793f1253dc46252a3bf6afafd6081c453a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 15:03:45 +0100 Subject: [PATCH 133/157] Add /show_config to telegram --- freqtrade/rpc/telegram.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8a81848ac..ba475e39e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -101,6 +101,7 @@ class Telegram(RPC): CommandHandler('edge', self._edge), CommandHandler('help', self._help), CommandHandler('version', self._version), + CommandHandler('show_config', self._show_config), ] for handle in handles: self._updater.dispatcher.add_handler(handle) @@ -550,6 +551,7 @@ class Telegram(RPC): "*/balance:* `Show account balance per currency`\n" \ "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \ "*/reload_conf:* `Reload configuration file` \n" \ + "*/show_config:* `Show running configuration` \n" \ "*/whitelist:* `Show current whitelist` \n" \ "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " \ "to the blacklist.` \n" \ @@ -570,6 +572,26 @@ class Telegram(RPC): """ self._send_msg('*Version:* `{}`'.format(__version__)) + @authorized_only + def _show_config(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /show_config. + Show config information information + :param bot: telegram bot + :param update: message update + :return: None + """ + val = self._rpc_show_config() + self._send_msg( + f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" + f"*Exchange:* `{val['exchange']}`\n" + f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n" + f"*Minimum ROI:* `{val['minimal_roi']}`\n" + f"*{'Trailing ' if val['trailing_stop'] else ''}Stoploss:* `{val['stoploss']}`\n" + f"*Ticker Interval:* `{val['ticker_interval']}`\n" + f"*Strategy:* `{val['strategy']}`'" + ) + def _send_msg(self, msg: str, parse_mode: ParseMode = ParseMode.MARKDOWN) -> None: """ Send given markdown message From 2b190e5638d8f65b92d3b3ef75bb34324bc8db35 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 15:05:56 +0100 Subject: [PATCH 134/157] Add documentation --- docs/rest-api.md | 5 +++++ docs/telegram-usage.md | 1 + 2 files changed, 6 insertions(+) diff --git a/docs/rest-api.md b/docs/rest-api.md index 70e090569..187a71c97 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -106,6 +106,7 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `stop` | | Stops the trader | `stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. | `reload_conf` | | Reloads the configuration file +| `show_config` | | Shows part of the current configuration with relevant settings to operation | `status` | | Lists all open trades | `count` | | Displays number of trades used and available | `profit` | | Display a summary of your profit/loss from close trades and some stats about your performance @@ -172,6 +173,10 @@ reload_conf Reload configuration :returns: json object +show_config + Returns part of the configuration, relevant for trading operations. + :return: json object containing the version + start Start the bot if it's in stopped state. :returns: json object diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 424b55faf..ed0c21a6e 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -53,6 +53,7 @@ official commands. You can ask at any moment for help with `/help`. | `/stop` | | Stops the trader | `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. | `/reload_conf` | | Reloads the configuration file +| `/show_config` | | Shows part of the current configuration with relevant settings to operation | `/status` | | Lists all open trades | `/status table` | | List all open trades in a table format | `/count` | | Displays number of trades used and available From e4e8a611be888e657ebdd5f7c5b61ac71846d388 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 15:12:53 +0100 Subject: [PATCH 135/157] Add tests for telegram --- freqtrade/rpc/rpc.py | 5 ++++- tests/rpc/test_rpc_telegram.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c78951a6d..8b557df96 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -80,7 +80,7 @@ class RPC: def send_msg(self, msg: Dict[str, str]) -> None: """ Sends a message to all registered rpc modules """ - def _rpc_show_config(self) -> Dict: + def _rpc_show_config(self) -> Dict[str, Any]: """ Return a dict of config options. Explicitly does NOT return the full config to avoid leakage of sensitive @@ -94,6 +94,9 @@ class RPC: 'minimal_roi': config['minimal_roi'].copy(), 'stoploss': config['stoploss'], 'trailing_stop': config['trailing_stop'], + 'trailing_stop_positive': config.get('trailing_stop_positive'), + 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), + 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached'), 'ticker_interval': config['ticker_interval'], 'exchange': config['exchange']['name'], 'strategy': config['strategy'], diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 766511d2d..73b38b808 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1174,6 +1174,23 @@ def test_version_handle(default_conf, update, mocker) -> None: assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] +def test_show_config_handle(default_conf, update, mocker) -> None: + msg_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.rpc.telegram.Telegram', + _init=MagicMock(), + _send_msg=msg_mock + ) + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + telegram = Telegram(freqtradebot) + + telegram._show_config(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0] + assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0] + assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0] + + def test_send_msg_buy_notification(default_conf, mocker) -> None: msg_mock = MagicMock() mocker.patch.multiple( From 547d65b06511515952dfa49156c4ab3ccbd75267 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 15:22:14 +0100 Subject: [PATCH 136/157] Fix broken test --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ba475e39e..0547af7b0 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -95,13 +95,13 @@ class Telegram(RPC): CommandHandler('daily', self._daily), CommandHandler('count', self._count), CommandHandler('reload_conf', self._reload_conf), + CommandHandler('show_config', self._show_config), CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), CommandHandler('blacklist', self._blacklist), CommandHandler('edge', self._edge), CommandHandler('help', self._help), CommandHandler('version', self._version), - CommandHandler('show_config', self._show_config), ] for handle in handles: self._updater.dispatcher.add_handler(handle) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 73b38b808..c6a8094ab 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -73,7 +73,7 @@ def test_init(default_conf, mocker, caplog) -> None: message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \ "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \ - "['performance'], ['daily'], ['count'], ['reload_conf'], " \ + "['performance'], ['daily'], ['count'], ['reload_conf'], ['show_config'], " \ "['stopbuy'], ['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]" assert log_has(message_str, caplog) From 599e18b9209adf9ea16cbb9ecd144fb6b4122bb9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:20:02 +0000 Subject: [PATCH 137/157] Bump urllib3 from 1.25.6 to 1.25.7 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.6 to 1.25.7. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/master/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.25.6...1.25.7) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 33a5d0776..a4521af71 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -6,7 +6,7 @@ python-telegram-bot==12.2.0 arrow==0.15.4 cachetools==3.1.1 requests==2.22.0 -urllib3==1.25.6 +urllib3==1.25.7 wrapt==1.11.2 jsonschema==3.1.1 TA-Lib==0.4.17 From 933564591dfc99ddd0473aebbd2634e742abe8c8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:20:30 +0000 Subject: [PATCH 138/157] Bump sqlalchemy from 1.3.10 to 1.3.11 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.10 to 1.3.11. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/master/CHANGES) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 33a5d0776..cbfcd8035 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,7 +1,7 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs ccxt==1.19.25 -SQLAlchemy==1.3.10 +SQLAlchemy==1.3.11 python-telegram-bot==12.2.0 arrow==0.15.4 cachetools==3.1.1 From 42474b714422141612d76cc21432d871bc85c1e6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:20:50 +0000 Subject: [PATCH 139/157] Bump flake8-tidy-imports from 3.0.0 to 3.1.0 Bumps [flake8-tidy-imports](https://github.com/adamchainz/flake8-tidy-imports) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/adamchainz/flake8-tidy-imports/releases) - [Changelog](https://github.com/adamchainz/flake8-tidy-imports/blob/master/HISTORY.rst) - [Commits](https://github.com/adamchainz/flake8-tidy-imports/compare/3.0.0...3.1.0) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f346439af..ca51a98d4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ coveralls==1.8.2 flake8==3.7.9 flake8-type-annotations==0.1.0 -flake8-tidy-imports==3.0.0 +flake8-tidy-imports==3.1.0 mypy==0.740 pytest==5.2.2 pytest-asyncio==0.10.0 From a33d4087809e8ecacc145b702568a371ccbd6e2a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:21:25 +0000 Subject: [PATCH 140/157] Bump plotly from 4.2.1 to 4.3.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 4.2.1 to 4.3.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v4.2.1...v4.3.0) Signed-off-by: dependabot-preview[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index 235c71896..87d5553b6 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==4.2.1 +plotly==4.3.0 From e7157faddd24ac74264998999be8ddecb95a1505 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:22:18 +0000 Subject: [PATCH 141/157] Bump python-rapidjson from 0.8.0 to 0.9.1 Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 0.8.0 to 0.9.1. - [Release notes](https://github.com/python-rapidjson/python-rapidjson/releases) - [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst) - [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v0.8.0...v0.9.1) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 33a5d0776..635b3aafe 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -17,7 +17,7 @@ coinmarketcap==5.0.3 py_find_1st==1.1.4 # Load ticker files 30% faster -python-rapidjson==0.8.0 +python-rapidjson==0.9.1 # Notify systemd sdnotify==0.3.2 From cb6b3e17a936c1496e61dfc6f1bbc4185b51d954 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:22:41 +0000 Subject: [PATCH 142/157] Bump tabulate from 0.8.5 to 0.8.6 Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.8.5 to 0.8.6. - [Release notes](https://github.com/astanin/python-tabulate/releases) - [Changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG) - [Commits](https://github.com/astanin/python-tabulate/compare/v0.8.5...v0.8.6) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 33a5d0776..1215521a9 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -10,7 +10,7 @@ urllib3==1.25.6 wrapt==1.11.2 jsonschema==3.1.1 TA-Lib==0.4.17 -tabulate==0.8.5 +tabulate==0.8.6 coinmarketcap==5.0.3 # find first, C search in arrays From 0bc71403ff87a2918a912c992d3e263422f0a85c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:24:39 +0000 Subject: [PATCH 143/157] Bump mkdocs-material from 4.4.3 to 4.5.0 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 4.4.3 to 4.5.0. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/4.4.3...4.5.0) Signed-off-by: dependabot-preview[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 5e7fe7084..bfa5c0d1e 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==4.4.3 +mkdocs-material==4.5.0 mdx_truly_sane_lists==1.2 From dddccf8f1af791cd8a7a6d6e3a36c33e08f4f987 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:39:12 +0000 Subject: [PATCH 144/157] Bump ccxt from 1.19.25 to 1.19.54 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.19.25 to 1.19.54. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/1.19.25...1.19.54) Signed-off-by: dependabot-preview[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 7997506ce..a0e9b2a0c 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.19.25 +ccxt==1.19.54 SQLAlchemy==1.3.11 python-telegram-bot==12.2.0 arrow==0.15.4 From cd6d2761197659b02463ca91497816c8ec4d963f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2019 07:42:57 +0000 Subject: [PATCH 145/157] Bump pytest from 5.2.2 to 5.2.4 Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.2.2 to 5.2.4. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/5.2.2...5.2.4) Signed-off-by: dependabot-preview[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ca51a98d4..297b95623 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ flake8==3.7.9 flake8-type-annotations==0.1.0 flake8-tidy-imports==3.1.0 mypy==0.740 -pytest==5.2.2 +pytest==5.2.4 pytest-asyncio==0.10.0 pytest-cov==2.8.1 pytest-mock==1.11.2 From c22b00b30303ba44f3dcf140b4fd8b70fb2e2f80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Nov 2019 06:34:54 +0100 Subject: [PATCH 146/157] move pairlist filters out of config[] --- config_full.json.example | 5 +---- docs/configuration.md | 22 +++++++------------- freqtrade/pairlist/LowPriceFilter.py | 8 +++---- freqtrade/pairlist/pairlistmanager.py | 2 +- tests/pairlist/test_pairlist.py | 30 +++++++++++++-------------- tests/rpc/test_rpc.py | 2 +- tests/rpc/test_rpc_telegram.py | 2 +- 7 files changed, 30 insertions(+), 41 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index ba53f47d6..270938237 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -61,10 +61,7 @@ } }, {"method": "PrecisionFilter"}, - {"method": "LowPriceFilter", - "config": { - "low_price_percent": 0.01 - } + {"method": "LowPriceFilter", "low_price_ratio": 0.01 } ], "exchange": { diff --git a/docs/configuration.md b/docs/configuration.md index 8a2b74ed5..120842cdb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -416,11 +416,9 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis ```json "pairlists": [{ "method": "VolumePairList", - "config": { - "number_assets": 20, - "sort_key": "quoteVolume", - "ttl": 1800, - } + "number_assets": 20, + "sort_key": "quoteVolume", + "ttl": 1800, ], ``` @@ -430,7 +428,7 @@ Filters low-value coins which would not allow setting a stoploss. #### Low Price Pair Filter -The `LowPriceFilter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_percent` ratio. +The `LowPriceFilter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_ratio` ratio. This option is disabled by default, and will only apply if set to <> 0. Calculation example: @@ -450,16 +448,12 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, "pairlists": [ { "method": "VolumePairList", - "config": { - "number_assets": 20, - "sort_key": "quoteVolume", - }, + "number_assets": 20, + "sort_key": "quoteVolume", }, {"method": "PrecisionFilter"}, - {"method": "LowPriceFilter", - "config": {"low_price_percent": 0.01} - } - }], + {"method": "LowPriceFilter", "low_price_ratio": 0.01} + ], ``` ## Switch to Dry-run mode diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/LowPriceFilter.py index 83b6a85e6..ff18b97c8 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/LowPriceFilter.py @@ -13,7 +13,7 @@ class LowPriceFilter(IPairList): pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - self._low_price_percent = pairlistconfig.get('low_price_percent', 0) + self._low_price_ratio = pairlistconfig.get('low_price_ratio', 0) @property def needstickers(self) -> bool: @@ -28,7 +28,7 @@ class LowPriceFilter(IPairList): """ Short whitelist method description - used for startup-messages """ - return f"{self.name} - Filtering pairs priced below {self._low_price_percent * 100}%." + return f"{self.name} - Filtering pairs priced below {self._low_price_ratio * 100}%." def _validate_ticker_lowprice(self, ticker) -> bool: """ @@ -41,7 +41,7 @@ class LowPriceFilter(IPairList): compare = ticker['last'] + 1 / pow(10, precision) changeperc = (compare - ticker['last']) / ticker['last'] - if changeperc > self._low_price_percent: + if changeperc > self._low_price_ratio: logger.info(f"Removed {ticker['symbol']} from whitelist, " f"because 1 unit is {changeperc * 100:.3f}%") return False @@ -63,7 +63,7 @@ class LowPriceFilter(IPairList): pairlist.remove(p) # Filter out assets which would not allow setting a stoploss - if self._low_price_percent and not self._validate_ticker_lowprice(ticker): + if self._low_price_ratio and not self._validate_ticker_lowprice(ticker): pairlist.remove(p) return pairlist diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 0734d7f8f..fa5382c37 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -32,7 +32,7 @@ class PairListManager(): exchange=exchange, pairlistmanager=self, config=config, - pairlistconfig=pl.get('config'), + pairlistconfig=pl, pairlist_pos=len(self._pairlists) ).pairlist self._tickers_needed = pairl.needstickers or self._tickers_needed diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 13f868c7a..9daaa5353 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -29,10 +29,8 @@ def whitelist_conf(default_conf): default_conf['pairlists'] = [ { "method": "VolumePairList", - "config": { - "number_assets": 5, - "sort_key": "quoteVolume", - } + "number_assets": 5, + "sort_key": "quoteVolume", }, ] return default_conf @@ -136,37 +134,37 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): @pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [ - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}], + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), # Different sorting depending on quote or bid volume - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "bidVolume"}}], + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], "BTC", ['HOT/BTC', 'FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}], + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], "USDT", ['ETH/USDT']), # No pair for ETH ... - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}], + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], "ETH", []), # Precisionfilter and quote volume - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}, + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), # Precisionfilter bid - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "bidVolume"}}, + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), # Lowpricefilter and VolumePairList - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}, - {"method": "LowPriceFilter", "config": {"low_price_percent": 0.03}}], + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + {"method": "LowPriceFilter", "low_price_ratio": 0.03}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), # Hot is removed by precision_filter, Fuel by low_price_filter. - ([{"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "quoteVolume"}}, + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}, - {"method": "LowPriceFilter", "config": {"low_price_percent": 0.02}} + {"method": "LowPriceFilter", "low_price_ratio": 0.02} ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), # StaticPairlist Only ([{"method": "StaticPairList"}, ], "BTC", ['ETH/BTC', 'TKN/BTC']), # Static Pairlist before VolumePairList - sorting changes ([{"method": "StaticPairList"}, - {"method": "VolumePairList", "config": {"number_assets": 5, "sort_key": "bidVolume"}}, + {"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, ], "BTC", ['TKN/BTC', 'ETH/BTC']), ]) def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, @@ -257,7 +255,7 @@ def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist def test_volumepairlist_invalid_sortvalue(mocker, markets, whitelist_conf): - whitelist_conf['pairlists'][0]['config'].update({"sort_key": "asdf"}) + whitelist_conf['pairlists'][0].update({"sort_key": "asdf"}) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) with pytest.raises(OperationalException, diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 8747fe6ff..edc0fdb8a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -726,7 +726,7 @@ def test_rpc_whitelist(mocker, default_conf) -> None: def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: default_conf['pairlists'] = [{'method': 'VolumePairList', - 'config': {'number_assets': 4} + 'number_assets': 4, }] mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index bb9d88658..52d58e887 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1063,7 +1063,7 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None: ) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) default_conf['pairlists'] = [{'method': 'VolumePairList', - 'config': {'number_assets': 4} + 'number_assets': 4 }] freqtradebot = get_patched_freqtradebot(mocker, default_conf) From a8855bf7955eed07a107e31ca53b0a418426c373 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Nov 2019 06:41:05 +0100 Subject: [PATCH 147/157] rename LowPriceFilter to PrieFilter --- config_full.json.example | 2 +- docs/configuration.md | 13 +++++++------ freqtrade/constants.py | 2 +- .../pairlist/{LowPriceFilter.py => PriceFilter.py} | 4 ++-- tests/pairlist/test_pairlist.py | 8 ++++---- 5 files changed, 15 insertions(+), 14 deletions(-) rename freqtrade/pairlist/{LowPriceFilter.py => PriceFilter.py} (96%) diff --git a/config_full.json.example b/config_full.json.example index 270938237..5ceeaff09 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -61,7 +61,7 @@ } }, {"method": "PrecisionFilter"}, - {"method": "LowPriceFilter", "low_price_ratio": 0.01 + {"method": "PriceFilter", "low_price_ratio": 0.01 } ], "exchange": { diff --git a/docs/configuration.md b/docs/configuration.md index 120842cdb..ed50b521e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -380,7 +380,7 @@ The valid values are: Pairlists define the list of pairs that the bot should trade. There are [`StaticPairList`](#static-pair-list) and dynamic Whitelists available. -[`PrecisionFilter`](#precision-filter) and [`LowPriceFilter`](#low-price-pair-filter) act as filters, removing low-value pairs. +[`PrecisionFilter`](#precision-filter) and [`PriceFilter`](#price-pair-filter) act as filters, removing low-value pairs. All pairlists can be chained, and a combination of all pairlists will become your new whitelist. Pairlists are executed in the sequence they are configured. You should always configure either `StaticPairList` or `DynamicPairList` as starting pairlists. @@ -391,7 +391,7 @@ Inactive markets and blacklisted pairs are always removed from the resulting `pa * [`StaticPairList`](#static-pair-list) (default, if not configured differently) * [`VolumePairList`](#volume-pair-list) * [`PrecisionFilter`](#precision-filter) -* [`LowPriceFilter`](#low-price-pair-filter) +* [`PriceFilter`](#price-pair-filter) #### Static Pair List @@ -426,9 +426,10 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis Filters low-value coins which would not allow setting a stoploss. -#### Low Price Pair Filter +#### Price Pair Filter -The `LowPriceFilter` allows filtering of pairs where a raise of 1 price unit is below the `low_price_ratio` ratio. +The `PriceFilter` allows filtering of pairs by price. +Currently, only `low_price_ratio` is implemented, where a raise of 1 price unit (pip) is below the `low_price_ratio` ratio. This option is disabled by default, and will only apply if set to <> 0. Calculation example: @@ -438,7 +439,7 @@ These pairs are dangerous since it may be impossible to place the desired stoplo ### Full Pairlist example -The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting by `quoteVolume` and applies both [`PrecisionFilter`](#precision-filter) and [`LowPriceFilter`](#low-price-pair-filter), filtering all assets where 1 priceunit is > 1%. +The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting by `quoteVolume` and applies both [`PrecisionFilter`](#precision-filter) and [`PriceFilter`](#price-pair-filter), filtering all assets where 1 priceunit is > 1%. ```json "exchange": { @@ -452,7 +453,7 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, "sort_key": "quoteVolume", }, {"method": "PrecisionFilter"}, - {"method": "LowPriceFilter", "low_price_ratio": 0.01} + {"method": "PriceFilter", "low_price_ratio": 0.01} ], ``` diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c98c32d4c..24e3589fd 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -20,7 +20,7 @@ REQUIRED_ORDERTIF = ['buy', 'sell'] REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange'] ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] -AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'LowPriceFilter'] +AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'PriceFilter'] DRY_RUN_WALLET = 999.9 MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons diff --git a/freqtrade/pairlist/LowPriceFilter.py b/freqtrade/pairlist/PriceFilter.py similarity index 96% rename from freqtrade/pairlist/LowPriceFilter.py rename to freqtrade/pairlist/PriceFilter.py index ff18b97c8..b3546ebd9 100644 --- a/freqtrade/pairlist/LowPriceFilter.py +++ b/freqtrade/pairlist/PriceFilter.py @@ -7,7 +7,7 @@ from freqtrade.pairlist.IPairList import IPairList logger = logging.getLogger(__name__) -class LowPriceFilter(IPairList): +class PriceFilter(IPairList): def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict, pairlist_pos: int) -> None: @@ -32,7 +32,7 @@ class LowPriceFilter(IPairList): def _validate_ticker_lowprice(self, ticker) -> bool: """ - Check if if one price-step is > than a certain barrier. + Check if if one price-step (pip) is > than a certain barrier. :param ticker: ticker dict as returned from ccxt.load_markets() :param precision: Precision :return: True if the pair can stay, false if it should be removed diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 9daaa5353..76537880c 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -150,14 +150,14 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): # Precisionfilter bid ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), - # Lowpricefilter and VolumePairList + # PriceFilter and VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, - {"method": "LowPriceFilter", "low_price_ratio": 0.03}], + {"method": "PriceFilter", "low_price_ratio": 0.03}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), # Hot is removed by precision_filter, Fuel by low_price_filter. ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}, - {"method": "LowPriceFilter", "low_price_ratio": 0.02} + {"method": "PriceFilter", "low_price_ratio": 0.02} ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), # StaticPairlist Only ([{"method": "StaticPairList"}, @@ -189,7 +189,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t if pairlist['method'] == 'PrecisionFilter': assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' r'would be <= stop limit.*', caplog) - if pairlist['method'] == 'LowPriceFilter': + if pairlist['method'] == 'PriceFilter': assert log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) From 5f62a9e4d8eed5d3c7a15eca92c041c84bf12b57 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Nov 2019 06:48:56 +0100 Subject: [PATCH 148/157] rename ttl to refresh_period --- config_full.json.example | 8 +++----- docs/configuration.md | 4 ++-- docs/developer.md | 2 +- freqtrade/pairlist/VolumePairList.py | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/config_full.json.example b/config_full.json.example index 5ceeaff09..b9631f63d 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -54,11 +54,9 @@ {"method": "StaticPairList"}, { "method": "VolumePairList", - "config": { - "number_assets": 20, - "sort_key": "quoteVolume", - "ttl": 1800 - } + "number_assets": 20, + "sort_key": "quoteVolume", + "refresh_period": 1800 }, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.01 diff --git a/docs/configuration.md b/docs/configuration.md index ed50b521e..946ee3bf7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -411,14 +411,14 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis `VolumePairList` considers outputs of previous pairlists unless it's the first configured pairlist, it does not consider `pair_whitelist`, but selects the top assets from all available markets (with matching stake-currency) on the exchange. -`ttl` allows setting the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). +`refresh_period` allows setting the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes). ```json "pairlists": [{ "method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume", - "ttl": 1800, + "refresh_period": 1800, ], ``` diff --git a/docs/developer.md b/docs/developer.md index 0eba6fd59..2d39893bc 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -115,7 +115,7 @@ Now, let's step through the methods which require actions: #### Pairlist configuration Configuration for PairListProvider is done in the bot configuration file in the element `"pairlist"`. -This Pairlist-object may contain a `"config"` dict with additional configurations for the configured pairlist. +This Pairlist-object may contain configurations with additional configurations for the configured pairlist. By convention, `"number_assets"` is used to specify the maximum number of pairs to keep in the whitelist. Please follow this to ensure a consistent user experience. Additional elements can be configured as needed. `VolumePairList` uses `"sort_key"` to specify the sorting value - however feel free to specify whatever is necessary for your great algorithm to be successfull and dynamic. diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index 708c8d7c2..2df9ba691 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -28,7 +28,7 @@ class VolumePairList(IPairList): 'for "pairlist.config.number_assets"') self._number_pairs = self._pairlistconfig['number_assets'] self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume') - self._ttl = self._pairlistconfig.get('ttl', 1800) + self.refresh_period = self._pairlistconfig.get('refresh_period', 1800) if not self._exchange.exchange_has('fetchTickers'): raise OperationalException( @@ -67,7 +67,7 @@ class VolumePairList(IPairList): :return: new whitelist """ # Generate dynamic whitelist - if self._last_refresh + self._ttl < datetime.now().timestamp(): + if self._last_refresh + self.refresh_period < datetime.now().timestamp(): self._last_refresh = int(datetime.now().timestamp()) return self._gen_pair_whitelist(pairlist, tickers, From 751157b4ea3ac291e904a01ed5319bec3d680d44 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 18 Nov 2019 19:32:35 +0100 Subject: [PATCH 149/157] Don't notify on builds from forks they don't have secrets available ATM --- .github/workflows/ci.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8932cf07..c74b1720e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,6 +73,8 @@ jobs: # Allow failure for coveralls # Fake travis environment to get coveralls working correctly export TRAVIS_PULL_REQUEST="https://github.com/${GITHUB_REPOSITORY}/pull/$(cat $GITHUB_EVENT_PATH | jq -r .number)" + export CI_BRANCH=${GITHUB_REF#"ref/heads"} + echo "${CI_BRANCH}" coveralls || true - name: Backtesting @@ -95,7 +97,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: always() + if: always() && github.repository.fork == true with: type: ${{ job.status }} job_name: '*Freqtrade CI ${{ matrix.os }}*' @@ -156,7 +158,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: always() + if: always() && github.repository.fork == true with: type: ${{ job.status }} job_name: '*Freqtrade CI windows*' @@ -176,7 +178,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: failure() + if: failure() && github.repository.fork == true with: type: ${{ job.status }} job_name: '*Freqtrade Docs*' @@ -186,7 +188,7 @@ jobs: deploy: needs: [ build, build_windows, docs_check ] runs-on: ubuntu-18.04 - if: github.event_name == 'push' || github.event_name == 'schedule' + if: (github.event_name == 'push' || github.event_name == 'schedule') && github.repository == 'freqtrade/freqtrade' steps: - uses: actions/checkout@v1 @@ -217,7 +219,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: always() + if: always() && github.repository.fork == true with: type: ${{ job.status }} job_name: '*Freqtrade CI Deploy*' From c92f233c1565174b4b8aa6f0ef2bbd57e1b1db2e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Nov 2019 19:33:04 +0100 Subject: [PATCH 150/157] Move settings to correct location --- freqtrade/configuration/deprecated_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 3aec85ae2..8f3dbd675 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -63,9 +63,9 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: "DEPRECATED: " f"Using VolumePairList in pairlist is deprecated and must be moved to pairlists. " "Please refer to the docs on configuration details") - config['pairlists'].append({'method': 'VolumePairList', - 'config': config.get('pairlist', {}).get('config') - }) + pl = {'method': 'VolumePairList'} + pl.update(config.get('pairlist', {}).get('config')) + config['pairlists'].append(pl) if config.get('pairlist', {}).get('config', {}).get('precision_filter'): logger.warning( From 633996216a2640a19454aa8bbdd40c86021bbbac Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 20 Nov 2019 15:20:39 +0300 Subject: [PATCH 151/157] Improve commands help list --- scripts/rest_client.py | 69 +++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/scripts/rest_client.py b/scripts/rest_client.py index 096286013..03e4fc76b 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -65,100 +65,99 @@ class FtRestClient(): return self._call("POST", apipath, params=params, data=data) def start(self): - """ - Start the bot if it's in stopped state. + """Start the bot if it's in the stopped state. + :return: json object """ return self._post("start") def stop(self): - """ - Stop the bot. Use start to restart + """Stop the bot. Use `start` to restart. + :return: json object """ return self._post("stop") def stopbuy(self): - """ - Stop buying (but handle sells gracefully). - use reload_conf to reset + """Stop buying (but handle sells gracefully). Use `reload_conf` to reset. + :return: json object """ return self._post("stopbuy") def reload_conf(self): - """ - Reload configuration + """Reload configuration. + :return: json object """ return self._post("reload_conf") def balance(self): - """ - Get the account balance + """Get the account balance. + :return: json object """ return self._get("balance") def count(self): - """ - Returns the amount of open trades + """Return the amount of open trades. + :return: json object """ return self._get("count") def daily(self, days=None): - """ - Returns the amount of open trades + """Return the amount of open trades. + :return: json object """ return self._get("daily", params={"timescale": days} if days else None) def edge(self): - """ - Returns information about edge + """Return information about edge. + :return: json object """ return self._get("edge") def profit(self): - """ - Returns the profit summary + """Return the profit summary. + :return: json object """ return self._get("profit") def performance(self): - """ - Returns the performance of the different coins + """Return the performance of the different coins. + :return: json object """ return self._get("performance") def status(self): - """ - Get the status of open trades + """Get the status of open trades. + :return: json object """ return self._get("status") def version(self): - """ - Returns the version of the bot + """Return the version of the bot. + :return: json object containing the version """ return self._get("version") def whitelist(self): - """ - Show the current whitelist + """Show the current whitelist. + :return: json object """ return self._get("whitelist") def blacklist(self, *args): - """ - Show the current blacklist + """Show the current blacklist. + :param add: List of coins to add (example: "BNB/BTC") :return: json object """ @@ -168,8 +167,8 @@ class FtRestClient(): return self._post("blacklist", data={"blacklist": args}) def forcebuy(self, pair, price=None): - """ - Buy an asset + """Buy an asset. + :param pair: Pair to buy (ETH/BTC) :param price: Optional - price to buy :return: json object of the trade @@ -180,8 +179,8 @@ class FtRestClient(): return self._post("forcebuy", data=data) def forcesell(self, tradeid): - """ - Force-sell a trade + """Force-sell a trade. + :param tradeid: Id of the trade (can be received via status command) :return: json object """ @@ -236,10 +235,10 @@ def load_config(configfile): def print_commands(): # Print dynamic help for the different commands using the commands doc-strings client = FtRestClient(None) - print("Possible commands:") + print("Possible commands:\n") for x, y in inspect.getmembers(client): if not x.startswith('_'): - print(f"{x} {getattr(client, x).__doc__}") + print(f"{x}""\n "f"{getattr(client, x).__doc__.splitlines()[0]}""\n") def main(args): From 5f88c4aad98d92b722709c4ce2978170d32494f5 Mon Sep 17 00:00:00 2001 From: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> Date: Wed, 20 Nov 2019 19:31:30 +0300 Subject: [PATCH 152/157] Add example of usage for Aroon, Aroon Oscillator --- user_data/strategies/sample_strategy.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/user_data/strategies/sample_strategy.py b/user_data/strategies/sample_strategy.py index 36dea65c9..77a2d261a 100644 --- a/user_data/strategies/sample_strategy.py +++ b/user_data/strategies/sample_strategy.py @@ -107,10 +107,16 @@ class SampleStrategy(IStrategy): # RSI dataframe['rsi'] = ta.RSI(dataframe) - # ADX dataframe['adx'] = ta.ADX(dataframe) + """ + # Aroon, Aroon Oscillator + aroon = ta.AROON(dataframe) + dataframe['aroonup'] = aroon['aroonup'] + dataframe['aroondown'] = aroon['aroondown'] + dataframe['aroonosc'] = ta.AROONOSC(dataframe) + # Awesome oscillator dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) From 8b639b5026a2b7cd745de08cf764f7ddefcea9fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Nov 2019 19:54:00 +0100 Subject: [PATCH 153/157] Remove only :return: --- scripts/rest_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/rest_client.py b/scripts/rest_client.py index 03e4fc76b..9e52de2bb 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -10,6 +10,7 @@ so it can be used as a standalone script. import argparse import inspect import json +import re import logging import sys from pathlib import Path @@ -238,7 +239,8 @@ def print_commands(): print("Possible commands:\n") for x, y in inspect.getmembers(client): if not x.startswith('_'): - print(f"{x}""\n "f"{getattr(client, x).__doc__.splitlines()[0]}""\n") + doc = re.sub(':return:.*', '', getattr(client, x).__doc__, flags=re.MULTILINE).rstrip() + print(f"{x}\n\t{doc}\n") def main(args): From 9aac080414c5d7b96870b1aebf8f15cbe5c232f3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Nov 2019 20:08:12 +0100 Subject: [PATCH 154/157] Fix 'remaining' bug when handling buy timeout --- freqtrade/freqtradebot.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 58f676aef..702e8483f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -787,7 +787,7 @@ class FreqtradeBot: continue # Check if trade is still actually open - if float(order['remaining']) == 0.0: + if float(order.get('remaining', 0.0)) == 0.0: self.wallets.update() continue @@ -813,7 +813,8 @@ class FreqtradeBot: }) def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool: - """Buy timeout - cancel order + """ + Buy timeout - cancel order :return: True if order was fully cancelled """ reason = "cancelled due to timeout" @@ -824,18 +825,21 @@ class FreqtradeBot: corder = order reason = "canceled on Exchange" - if corder['remaining'] == corder['amount']: + if corder.get('remaining', order['remaining']) == order['amount']: # if trade is not partially completed, just delete the trade self.handle_buy_order_full_cancel(trade, reason) return True # if trade is partially complete, edit the stake details for the trade # and close the order - trade.amount = corder['amount'] - corder['remaining'] + # cancel_order may not contain the full order dict, so we need to fallback + # to the order dict aquired before cancelling. + # we need to fall back to the values from order if corder does not contain these keys. + trade.amount = order['amount'] - corder.get('remaining', order['remaining']) trade.stake_amount = trade.amount * trade.open_rate # verify if fees were taken from amount to avoid problems during selling try: - new_amount = self.get_real_amount(trade, corder, trade.amount) + new_amount = self.get_real_amount(trade, order, trade.amount) if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): trade.amount = new_amount # Fee was applied, so set to 0 From a5bd4e329a9da98191e20b46e15fc496d55ec2c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Nov 2019 20:33:05 +0100 Subject: [PATCH 155/157] improve cancel_order handling --- freqtrade/freqtradebot.py | 3 ++- tests/test_freqtradebot.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 702e8483f..358c63f90 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -839,7 +839,8 @@ class FreqtradeBot: trade.stake_amount = trade.amount * trade.open_rate # verify if fees were taken from amount to avoid problems during selling try: - new_amount = self.get_real_amount(trade, order, trade.amount) + new_amount = self.get_real_amount(trade, corder if 'fee' in corder else order, + trade.amount) if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC): trade.amount = new_amount # Fee was applied, so set to 0 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index f3baff7ce..c195ce39b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1804,7 +1804,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, open_trade, fee, mocker) -> None: rpc_mock = patch_RPCManager(mocker) - cancel_order_mock = MagicMock() + cancel_order_mock = MagicMock(return_value=limit_buy_order_old) patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', From eac01960a79e2031630e90169e23259194275fa2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Nov 2019 20:37:46 +0100 Subject: [PATCH 156/157] Add testcase for empty-order case --- tests/test_freqtradebot.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index c195ce39b..b01c8e247 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2089,6 +2089,29 @@ def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> Non assert cancel_order_mock.call_count == 1 +def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_order) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + cancel_order_mock = MagicMock(return_value={}) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + cancel_order=cancel_order_mock + ) + + freqtrade = FreqtradeBot(default_conf) + + Trade.session = MagicMock() + trade = MagicMock() + limit_buy_order['remaining'] = limit_buy_order['amount'] + assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order) + assert cancel_order_mock.call_count == 1 + + cancel_order_mock.reset_mock() + limit_buy_order['amount'] = 2 + assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order) + assert cancel_order_mock.call_count == 1 + + def test_handle_timedout_limit_sell(mocker, default_conf) -> None: patch_RPCManager(mocker) patch_exchange(mocker) From b8aa727edf27f7b2d00d0fe2b7e4eeeb44b87943 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Thu, 21 Nov 2019 04:59:38 +0300 Subject: [PATCH 157/157] Fix second part of freqtrade-strategies #51 --- freqtrade/strategy/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d42f8e989..e208138e7 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -109,8 +109,8 @@ class IStrategy(ABC): # Class level variables (intentional) containing # the dataprovider (dp) (access to other candles, historic data, ...) # and wallets - access to the current balance. - dp: DataProvider - wallets: Wallets + dp: Optional[DataProvider] = None + wallets: Optional[Wallets] = None def __init__(self, config: dict) -> None: self.config = config

    d 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