From 258d4bd6ae7ed19b4f682029c86d66974e2032f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 12:57:38 +0100 Subject: [PATCH 01/76] move sample-files from user_data to templates folder --- {user_data/hyperopts => freqtrade/templates}/sample_hyperopt.py | 0 .../hyperopts => freqtrade/templates}/sample_hyperopt_advanced.py | 0 .../hyperopts => freqtrade/templates}/sample_hyperopt_loss.py | 0 {user_data/strategies => freqtrade/templates}/sample_strategy.py | 0 user_data/hyperopts/__init__.py | 0 user_data/strategies/__init__.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {user_data/hyperopts => freqtrade/templates}/sample_hyperopt.py (100%) rename {user_data/hyperopts => freqtrade/templates}/sample_hyperopt_advanced.py (100%) rename {user_data/hyperopts => freqtrade/templates}/sample_hyperopt_loss.py (100%) rename {user_data/strategies => freqtrade/templates}/sample_strategy.py (100%) delete mode 100644 user_data/hyperopts/__init__.py delete mode 100644 user_data/strategies/__init__.py diff --git a/user_data/hyperopts/sample_hyperopt.py b/freqtrade/templates/sample_hyperopt.py similarity index 100% rename from user_data/hyperopts/sample_hyperopt.py rename to freqtrade/templates/sample_hyperopt.py diff --git a/user_data/hyperopts/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py similarity index 100% rename from user_data/hyperopts/sample_hyperopt_advanced.py rename to freqtrade/templates/sample_hyperopt_advanced.py diff --git a/user_data/hyperopts/sample_hyperopt_loss.py b/freqtrade/templates/sample_hyperopt_loss.py similarity index 100% rename from user_data/hyperopts/sample_hyperopt_loss.py rename to freqtrade/templates/sample_hyperopt_loss.py diff --git a/user_data/strategies/sample_strategy.py b/freqtrade/templates/sample_strategy.py similarity index 100% rename from user_data/strategies/sample_strategy.py rename to freqtrade/templates/sample_strategy.py diff --git a/user_data/hyperopts/__init__.py b/user_data/hyperopts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/user_data/strategies/__init__.py b/user_data/strategies/__init__.py deleted file mode 100644 index e69de29bb..000000000 From fd45ebd0e9d8ef49f00f2ccf3f8145debbdd2db8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 13:28:35 +0100 Subject: [PATCH 02/76] Copy templates when creating userdir --- .../configuration/directory_operations.py | 23 ++++++++++++- freqtrade/constants.py | 8 +++++ freqtrade/utils.py | 5 +-- tests/test_configuration.py | 34 ++++++++++++++++++- 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/freqtrade/configuration/directory_operations.py b/freqtrade/configuration/directory_operations.py index 395accd90..e39c485f3 100644 --- a/freqtrade/configuration/directory_operations.py +++ b/freqtrade/configuration/directory_operations.py @@ -1,8 +1,10 @@ import logging -from typing import Any, Dict, Optional +import shutil from pathlib import Path +from typing import Any, Dict, Optional from freqtrade import OperationalException +from freqtrade.constants import USER_DATA_FILES logger = logging.getLogger(__name__) @@ -48,3 +50,22 @@ def create_userdata_dir(directory: str, create_dir=False) -> Path: if not subfolder.is_dir(): subfolder.mkdir(parents=False) return folder + + +def copy_sample_files(directory: Path) -> None: + """ + Copy files from templates to User data directory. + :param directory: Directory to copy data to + """ + if not directory.is_dir(): + raise OperationalException(f"Directory `{directory}` does not exist.") + sourcedir = Path(__file__).parents[1] / "templates" + for source, target in USER_DATA_FILES.items(): + targetdir = directory / target + if not targetdir.is_dir(): + raise OperationalException(f"Directory `{targetdir}` does not exist.") + targetfile = targetdir / source + if targetfile.exists(): + logger.warning(f"File `{targetfile}` exists already, not deploying sample file.") + continue + shutil.copy(str(sourcedir / source), str(targetfile)) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a92371bc3..e28016eea 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -22,6 +22,14 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'P DRY_RUN_WALLET = 999.9 MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons +# Soure files with destination directories +USER_DATA_FILES = { + 'sample_strategy.py': 'strategies', + 'sample_hyperopt_advanced.py': 'hyperopts', + 'sample_hyperopt_loss.py': 'hyperopts', + 'sample_hyperopt.py': 'hyperopts', +} + TIMEFRAMES = [ '1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '6h', '8h', '12h', diff --git a/freqtrade/utils.py b/freqtrade/utils.py index b8ab7504e..c6422d04c 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -11,7 +11,7 @@ from tabulate import tabulate from freqtrade import OperationalException from freqtrade.configuration import Configuration, TimeRange, remove_credentials -from freqtrade.configuration.directory_operations import create_userdata_dir +from freqtrade.configuration.directory_operations import create_userdata_dir, copy_sample_files from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) @@ -81,7 +81,8 @@ def start_create_userdir(args: Dict[str, Any]) -> None: :return: None """ if "user_data_dir" in args and args["user_data_dir"]: - create_userdata_dir(args["user_data_dir"], create_dir=True) + userdir = create_userdata_dir(args["user_data_dir"], create_dir=True) + copy_sample_files(userdir) else: logger.warning("`create-userdir` requires --userdir to be set.") sys.exit(1) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 240b7c784..d74d64c95 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -18,7 +18,7 @@ from freqtrade.configuration.deprecated_settings import ( check_conflicting_settings, process_deprecated_setting, process_temporary_deprecated_settings) from freqtrade.configuration.directory_operations import (create_datadir, - create_userdata_dir) + create_userdata_dir, copy_sample_files) from freqtrade.configuration.load_config import load_config_file from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL from freqtrade.loggers import _set_loggers @@ -709,6 +709,38 @@ def test_create_userdata_dir_exists_exception(mocker, default_conf, caplog) -> N assert md.call_count == 0 +def test_copy_sample_files(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + copymock = mocker.patch('shutil.copy', MagicMock()) + + copy_sample_files(Path('/tmp/bar')) + assert copymock.call_count == 4 + assert copymock.call_args_list[0][0][1] == '/tmp/bar/strategies/sample_strategy.py' + assert copymock.call_args_list[1][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_advanced.py' + assert copymock.call_args_list[2][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_loss.py' + assert copymock.call_args_list[3][0][1] == '/tmp/bar/hyperopts/sample_hyperopt.py' + + +def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + mocker.patch('shutil.copy', MagicMock()) + with pytest.raises(OperationalException, + match=r"Directory `.{1,2}tmp.{1,2}bar` does not exist\."): + copy_sample_files(Path('/tmp/bar')) + + mocker.patch.object(Path, "is_dir", MagicMock(side_effect=[True, False])) + + with pytest.raises(OperationalException, + match=r"Directory `.{1,2}tmp.{1,2}bar.{1,2}strategies` does not exist\."): + copy_sample_files(Path('/tmp/bar')) + mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) + mocker.patch.object(Path, "exists", MagicMock(return_value=True)) + copy_sample_files(Path('/tmp/bar')) + assert log_has_re(r"File `.*` exists already, not deploying sample.*", caplog) + + def test_validate_tsl(default_conf): default_conf['stoploss'] = 0.0 with pytest.raises(OperationalException, match='The config stoploss needs to be different ' From 1d2ef5c2ce8d1fd0ce9310aaa38dbaeba0ca6258 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 13:30:26 +0100 Subject: [PATCH 03/76] Extract directory_operation tests to it's own test file --- tests/test_configuration.py | 73 -------------------------- tests/test_directory_operations.py | 82 ++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 73 deletions(-) create mode 100644 tests/test_directory_operations.py diff --git a/tests/test_configuration.py b/tests/test_configuration.py index d74d64c95..e971d15ab 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -17,8 +17,6 @@ from freqtrade.configuration.config_validation import validate_config_schema from freqtrade.configuration.deprecated_settings import ( check_conflicting_settings, process_deprecated_setting, process_temporary_deprecated_settings) -from freqtrade.configuration.directory_operations import (create_datadir, - create_userdata_dir, copy_sample_files) from freqtrade.configuration.load_config import load_config_file from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL from freqtrade.loggers import _set_loggers @@ -670,77 +668,6 @@ def test_validate_default_conf(default_conf) -> None: validate(default_conf, constants.CONF_SCHEMA, Draft4Validator) -def test_create_datadir(mocker, default_conf, caplog) -> None: - mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) - md = mocker.patch.object(Path, 'mkdir', MagicMock()) - - create_datadir(default_conf, '/foo/bar') - assert md.call_args[1]['parents'] is True - assert log_has('Created data directory: /foo/bar', caplog) - - -def test_create_userdata_dir(mocker, default_conf, caplog) -> None: - mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) - md = mocker.patch.object(Path, 'mkdir', MagicMock()) - - x = create_userdata_dir('/tmp/bar', create_dir=True) - assert md.call_count == 7 - assert md.call_args[1]['parents'] is False - assert log_has(f'Created user-data directory: {Path("/tmp/bar")}', caplog) - assert isinstance(x, Path) - assert str(x) == str(Path("/tmp/bar")) - - -def test_create_userdata_dir_exists(mocker, default_conf, caplog) -> None: - mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) - md = mocker.patch.object(Path, 'mkdir', MagicMock()) - - create_userdata_dir('/tmp/bar') - assert md.call_count == 0 - - -def test_create_userdata_dir_exists_exception(mocker, default_conf, caplog) -> None: - mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) - md = mocker.patch.object(Path, 'mkdir', MagicMock()) - - with pytest.raises(OperationalException, - match=r'Directory `.{1,2}tmp.{1,2}bar` does not exist.*'): - create_userdata_dir('/tmp/bar', create_dir=False) - assert md.call_count == 0 - - -def test_copy_sample_files(mocker, default_conf, caplog) -> None: - mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) - mocker.patch.object(Path, "exists", MagicMock(return_value=False)) - copymock = mocker.patch('shutil.copy', MagicMock()) - - copy_sample_files(Path('/tmp/bar')) - assert copymock.call_count == 4 - assert copymock.call_args_list[0][0][1] == '/tmp/bar/strategies/sample_strategy.py' - assert copymock.call_args_list[1][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_advanced.py' - assert copymock.call_args_list[2][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_loss.py' - assert copymock.call_args_list[3][0][1] == '/tmp/bar/hyperopts/sample_hyperopt.py' - - -def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None: - mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) - mocker.patch.object(Path, "exists", MagicMock(return_value=False)) - mocker.patch('shutil.copy', MagicMock()) - with pytest.raises(OperationalException, - match=r"Directory `.{1,2}tmp.{1,2}bar` does not exist\."): - copy_sample_files(Path('/tmp/bar')) - - mocker.patch.object(Path, "is_dir", MagicMock(side_effect=[True, False])) - - with pytest.raises(OperationalException, - match=r"Directory `.{1,2}tmp.{1,2}bar.{1,2}strategies` does not exist\."): - copy_sample_files(Path('/tmp/bar')) - mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) - mocker.patch.object(Path, "exists", MagicMock(return_value=True)) - copy_sample_files(Path('/tmp/bar')) - assert log_has_re(r"File `.*` exists already, not deploying sample.*", caplog) - - def test_validate_tsl(default_conf): default_conf['stoploss'] = 0.0 with pytest.raises(OperationalException, match='The config stoploss needs to be different ' diff --git a/tests/test_directory_operations.py b/tests/test_directory_operations.py new file mode 100644 index 000000000..a7d98795b --- /dev/null +++ b/tests/test_directory_operations.py @@ -0,0 +1,82 @@ +# pragma pylint: disable=missing-docstring, protected-access, invalid-name +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from freqtrade import OperationalException +from freqtrade.configuration.directory_operations import (copy_sample_files, + create_datadir, + create_userdata_dir) +from tests.conftest import log_has, log_has_re + + +def test_create_datadir(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) + md = mocker.patch.object(Path, 'mkdir', MagicMock()) + + create_datadir(default_conf, '/foo/bar') + assert md.call_args[1]['parents'] is True + assert log_has('Created data directory: /foo/bar', caplog) + + +def test_create_userdata_dir(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) + md = mocker.patch.object(Path, 'mkdir', MagicMock()) + + x = create_userdata_dir('/tmp/bar', create_dir=True) + assert md.call_count == 7 + assert md.call_args[1]['parents'] is False + assert log_has(f'Created user-data directory: {Path("/tmp/bar")}', caplog) + assert isinstance(x, Path) + assert str(x) == str(Path("/tmp/bar")) + + +def test_create_userdata_dir_exists(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) + md = mocker.patch.object(Path, 'mkdir', MagicMock()) + + create_userdata_dir('/tmp/bar') + assert md.call_count == 0 + + +def test_create_userdata_dir_exists_exception(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) + md = mocker.patch.object(Path, 'mkdir', MagicMock()) + + with pytest.raises(OperationalException, + match=r'Directory `.{1,2}tmp.{1,2}bar` does not exist.*'): + create_userdata_dir('/tmp/bar', create_dir=False) + assert md.call_count == 0 + + +def test_copy_sample_files(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + copymock = mocker.patch('shutil.copy', MagicMock()) + + copy_sample_files(Path('/tmp/bar')) + assert copymock.call_count == 4 + assert copymock.call_args_list[0][0][1] == '/tmp/bar/strategies/sample_strategy.py' + assert copymock.call_args_list[1][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_advanced.py' + assert copymock.call_args_list[2][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_loss.py' + assert copymock.call_args_list[3][0][1] == '/tmp/bar/hyperopts/sample_hyperopt.py' + + +def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None: + mocker.patch.object(Path, "is_dir", MagicMock(return_value=False)) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + mocker.patch('shutil.copy', MagicMock()) + with pytest.raises(OperationalException, + match=r"Directory `.{1,2}tmp.{1,2}bar` does not exist\."): + copy_sample_files(Path('/tmp/bar')) + + mocker.patch.object(Path, "is_dir", MagicMock(side_effect=[True, False])) + + with pytest.raises(OperationalException, + match=r"Directory `.{1,2}tmp.{1,2}bar.{1,2}strategies` does not exist\."): + copy_sample_files(Path('/tmp/bar')) + mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) + mocker.patch.object(Path, "exists", MagicMock(return_value=True)) + copy_sample_files(Path('/tmp/bar')) + assert log_has_re(r"File `.*` exists already, not deploying sample.*", caplog) From 084efc98d74cb4ab144b48d86be7f72423762fd8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 13:41:25 +0100 Subject: [PATCH 04/76] Address test-failures due to file moves --- tests/optimize/test_backtesting.py | 1 + tests/strategy/test_strategy.py | 8 +++++--- tests/test_utils.py | 2 ++ user_data/hyperopts/.gitkeep | 0 user_data/strategies/.gitkeep | 0 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 user_data/hyperopts/.gitkeep create mode 100644 user_data/strategies/.gitkeep diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index b722dd3f8..e74ead33d 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -869,6 +869,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'backtesting', '--config', 'config.json', '--datadir', str(testdatadir), + '--strategy-path', str(Path(__file__).parents[2] / 'freqtrade/templates'), '--ticker-interval', '1m', '--timerange', '1510694220-1510700340', '--enable-position-stacking', diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 97affc99c..2b84bc6ee 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -36,13 +36,15 @@ def test_search_strategy(): def test_load_strategy(default_conf, result): - default_conf.update({'strategy': 'SampleStrategy'}) + default_conf.update({'strategy': 'SampleStrategy', + 'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates') + }) resolver = StrategyResolver(default_conf) assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) def test_load_strategy_base64(result, caplog, default_conf): - with open("user_data/strategies/sample_strategy.py", "rb") as file: + with (Path(__file__).parents[2] / 'freqtrade/templates/sample_strategy.py').open("rb") as file: encoded_string = urlsafe_b64encode(file.read()).decode("utf-8") default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)}) @@ -57,7 +59,7 @@ 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) + resolver._load_strategy('DefaultStrategy', config=default_conf, extra_dir=extra_dir) assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog) diff --git a/tests/test_utils.py b/tests/test_utils.py index bbb4fc648..88c9af35d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -442,6 +442,7 @@ def test_create_datadir_failed(caplog): def test_create_datadir(caplog, mocker): cud = mocker.patch("freqtrade.utils.create_userdata_dir", MagicMock()) + csf = mocker.patch("freqtrade.utils.copy_sample_files", MagicMock()) args = [ "create-userdir", "--userdir", @@ -450,6 +451,7 @@ def test_create_datadir(caplog, mocker): start_create_userdir(get_args(args)) assert cud.call_count == 1 + assert csf.call_count == 1 assert len(caplog.record_tuples) == 0 diff --git a/user_data/hyperopts/.gitkeep b/user_data/hyperopts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/user_data/strategies/.gitkeep b/user_data/strategies/.gitkeep new file mode 100644 index 000000000..e69de29bb From 471bd4d889fa2343bdc028755356ce0f212e2da7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 13:52:59 +0100 Subject: [PATCH 05/76] Small stylistic fixes --- freqtrade/constants.py | 1 + freqtrade/templates/sample_hyperopt_advanced.py | 4 +--- .../templates}/strategy_analysis_example.ipynb | 0 tests/test_directory_operations.py | 3 ++- 4 files changed, 4 insertions(+), 4 deletions(-) rename {user_data/notebooks => freqtrade/templates}/strategy_analysis_example.ipynb (100%) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index e28016eea..96109bc94 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -28,6 +28,7 @@ USER_DATA_FILES = { 'sample_hyperopt_advanced.py': 'hyperopts', 'sample_hyperopt_loss.py': 'hyperopts', 'sample_hyperopt.py': 'hyperopts', + 'strategy_analysis_example.ipynb': 'notebooks', } TIMEFRAMES = [ diff --git a/freqtrade/templates/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py index 66182edcf..7ababc16c 100644 --- a/freqtrade/templates/sample_hyperopt_advanced.py +++ b/freqtrade/templates/sample_hyperopt_advanced.py @@ -1,11 +1,9 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement from functools import reduce -from math import exp from typing import Any, Callable, Dict, List -from datetime import datetime -import numpy as np# noqa F401 +import numpy as np # noqa F401 import talib.abstract as ta from pandas import DataFrame from skopt.space import Categorical, Dimension, Integer, Real diff --git a/user_data/notebooks/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb similarity index 100% rename from user_data/notebooks/strategy_analysis_example.ipynb rename to freqtrade/templates/strategy_analysis_example.ipynb diff --git a/tests/test_directory_operations.py b/tests/test_directory_operations.py index a7d98795b..5c2485fc3 100644 --- a/tests/test_directory_operations.py +++ b/tests/test_directory_operations.py @@ -56,11 +56,12 @@ def test_copy_sample_files(mocker, default_conf, caplog) -> None: copymock = mocker.patch('shutil.copy', MagicMock()) copy_sample_files(Path('/tmp/bar')) - assert copymock.call_count == 4 + assert copymock.call_count == 5 assert copymock.call_args_list[0][0][1] == '/tmp/bar/strategies/sample_strategy.py' assert copymock.call_args_list[1][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_advanced.py' assert copymock.call_args_list[2][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_loss.py' assert copymock.call_args_list[3][0][1] == '/tmp/bar/hyperopts/sample_hyperopt.py' + assert copymock.call_args_list[4][0][1] == '/tmp/bar/notebooks/strategy_analysis_example.ipynb' def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None: From 19b1a6c6381c9541ca0db2f11b06b90916c70f93 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 13:55:26 +0100 Subject: [PATCH 06/76] create-userdir should create the notebooks folder, too --- freqtrade/configuration/directory_operations.py | 3 ++- tests/test_directory_operations.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/directory_operations.py b/freqtrade/configuration/directory_operations.py index e39c485f3..8837c3572 100644 --- a/freqtrade/configuration/directory_operations.py +++ b/freqtrade/configuration/directory_operations.py @@ -33,7 +33,8 @@ def create_userdata_dir(directory: str, create_dir=False) -> Path: :param create_dir: Create directory if it does not exist. :return: Path object containing the directory """ - sub_dirs = ["backtest_results", "data", "hyperopts", "hyperopt_results", "plot", "strategies", ] + sub_dirs = ["backtest_results", "data", "hyperopts", "hyperopt_results", "notebooks", + "plot", "strategies", ] folder = Path(directory) if not folder.is_dir(): if create_dir: diff --git a/tests/test_directory_operations.py b/tests/test_directory_operations.py index 5c2485fc3..064b5b6a3 100644 --- a/tests/test_directory_operations.py +++ b/tests/test_directory_operations.py @@ -25,7 +25,7 @@ def test_create_userdata_dir(mocker, default_conf, caplog) -> None: md = mocker.patch.object(Path, 'mkdir', MagicMock()) x = create_userdata_dir('/tmp/bar', create_dir=True) - assert md.call_count == 7 + assert md.call_count == 8 assert md.call_args[1]['parents'] is False assert log_has(f'Created user-data directory: {Path("/tmp/bar")}', caplog) assert isinstance(x, Path) From 41494f28da2a678c8b685f5a462ffebcb180457f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 14:08:55 +0100 Subject: [PATCH 07/76] Allow resetting of the directory --- freqtrade/configuration/arguments.py | 2 +- freqtrade/configuration/cli_options.py | 5 +++++ freqtrade/configuration/directory_operations.py | 10 +++++++--- freqtrade/utils.py | 3 ++- tests/test_directory_operations.py | 5 ++++- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 29d0d98a2..149e28d2b 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -37,7 +37,7 @@ ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"] ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column", "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] -ARGS_CREATE_USERDIR = ["user_data_dir"] +ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange", "timeframes", "erase"] diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 6dc5ef026..d7a496aa7 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -62,6 +62,11 @@ AVAILABLE_CLI_OPTIONS = { help='Path to userdata directory.', metavar='PATH', ), + "reset": Arg( + '--reset', + help='Reset sample files to their original state.', + action='store_true', + ), # Main options "strategy": Arg( '-s', '--strategy', diff --git a/freqtrade/configuration/directory_operations.py b/freqtrade/configuration/directory_operations.py index 8837c3572..3dd76a025 100644 --- a/freqtrade/configuration/directory_operations.py +++ b/freqtrade/configuration/directory_operations.py @@ -53,10 +53,11 @@ def create_userdata_dir(directory: str, create_dir=False) -> Path: return folder -def copy_sample_files(directory: Path) -> None: +def copy_sample_files(directory: Path, overwrite: bool = False) -> None: """ Copy files from templates to User data directory. :param directory: Directory to copy data to + :param overwrite: Overwrite existing sample files """ if not directory.is_dir(): raise OperationalException(f"Directory `{directory}` does not exist.") @@ -67,6 +68,9 @@ def copy_sample_files(directory: Path) -> None: raise OperationalException(f"Directory `{targetdir}` does not exist.") targetfile = targetdir / source if targetfile.exists(): - logger.warning(f"File `{targetfile}` exists already, not deploying sample file.") - continue + if not overwrite: + logger.warning(f"File `{targetfile}` exists already, not deploying sample file.") + continue + else: + logger.warning(f"File `{targetfile}` exists already, overwriting.") shutil.copy(str(sourcedir / source), str(targetfile)) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index c6422d04c..b9730da10 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -1,3 +1,4 @@ +from freqtrade.loggers import setup_logging import logging import sys from collections import OrderedDict @@ -82,7 +83,7 @@ def start_create_userdir(args: Dict[str, Any]) -> None: """ if "user_data_dir" in args and args["user_data_dir"]: userdir = create_userdata_dir(args["user_data_dir"], create_dir=True) - copy_sample_files(userdir) + copy_sample_files(userdir, overwrite=args["reset"]) else: logger.warning("`create-userdir` requires --userdir to be set.") sys.exit(1) diff --git a/tests/test_directory_operations.py b/tests/test_directory_operations.py index 064b5b6a3..c354b40b0 100644 --- a/tests/test_directory_operations.py +++ b/tests/test_directory_operations.py @@ -80,4 +80,7 @@ def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None: mocker.patch.object(Path, "is_dir", MagicMock(return_value=True)) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) copy_sample_files(Path('/tmp/bar')) - assert log_has_re(r"File `.*` exists already, not deploying sample.*", caplog) + assert log_has_re(r"File `.*` exists already, not deploying sample file\.", caplog) + caplog.clear() + copy_sample_files(Path('/tmp/bar'), overwrite=True) + assert log_has_re(r"File `.*` exists already, overwriting\.", caplog) From ed1d4500996dd86516d29b7bf4e840610a71b5b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 15:02:42 +0100 Subject: [PATCH 08/76] Update documentation for create-userdir util --- .coveragerc | 1 + docs/utils.md | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/.coveragerc b/.coveragerc index 96ad6b09b..74dccbfe1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,7 @@ [run] omit = scripts/* + freqtrade/templates/* freqtrade/vendor/* freqtrade/__main__.py tests/* diff --git a/docs/utils.md b/docs/utils.md index 9f5792660..d9baee32c 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -2,6 +2,41 @@ Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section. +## Create userdir + +Creates the directory structure to hold your files for freqtrade. +Will also create strategy and hyperopt examples for you to get started. +Can be used multiple times - using `--reset` will reset the sample strategy and hyperopt files to their default state. + +``` +usage: freqtrade create-userdir [-h] [--userdir PATH] [--reset] + +optional arguments: + -h, --help show this help message and exit + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + --reset Reset sample files to their original state. +``` + +!!! Warning + Using `--reset` may result in loss of data, since this will overwrite all sample files without asking again. + +``` +├── backtest_results +├── data +├── hyperopt_results +├── hyperopts +│   ├── sample_hyperopt_advanced.py +│   ├── sample_hyperopt_loss.py +│   └── sample_hyperopt.py +├── notebooks +│   └── strategy_analysis_example.ipynb +├── plot +└── strategies + └── sample_strategy.py +``` + + ## List Exchanges Use the `list-exchanges` subcommand to see the exchanges available for the bot. From 8cf8ab089e8b962a4d2a68e293bc01c5c769f163 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 15:07:36 +0100 Subject: [PATCH 09/76] Add note about create-datadir to install instruction --- docs/developer.md | 4 ++-- docs/hyperopt.md | 6 +++--- docs/installation.md | 26 ++++++++++++++------------ docs/strategy-customization.md | 6 +++--- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/developer.md b/docs/developer.md index ab647726c..d731f1768 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -200,8 +200,8 @@ If the day shows the same day, then the last candle can be assumed as incomplete To keep the jupyter notebooks aligned with the documentation, the following should be ran after updating a example notebook. ``` bash -jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace user_data/notebooks/strategy_analysis_example.ipynb -jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown user_data/notebooks/strategy_analysis_example.ipynb --stdout > docs/strategy_analysis_example.md +jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace freqtrade/templates/strategy_analysis_example.ipynb +jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown freqtrade/templates/strategy_analysis_example.ipynb --stdout > docs/strategy_analysis_example.md ``` ## Continuous integration diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 6c1505e75..8a750ef43 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -15,7 +15,7 @@ To learn how to get data for the pairs and exchange you're interrested in, head ## Prepare Hyperopting Before we start digging into Hyperopt, we recommend you to take a look at -the sample hyperopt file located in [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt.py). +the sample hyperopt file located in [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt.py). Configuring hyperopt is similar to writing your own strategy, and many tasks will be similar and a lot of code can be copied across from the strategy. @@ -423,7 +423,7 @@ These ranges should be sufficient in most cases. The minutes in the steps (ROI d If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default. -Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps). A sample for these methods can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py). +Override the `roi_space()` method if you need components of the ROI tables to vary in other ranges. Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization if you need a different structure of the ROI tables or other amount of rows (steps). A sample for these methods can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). ### Understand Hyperopt Stoploss results @@ -458,7 +458,7 @@ If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimiza If you have the `stoploss_space()` method in your custom hyperopt file, remove it in order to utilize Stoploss hyperoptimization space generated by Freqtrade by default. -Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py). +Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py). ### Validate backtesting results diff --git a/docs/installation.md b/docs/installation.md index 593da2acf..411441aa2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -162,7 +162,7 @@ Clone the git repository: ```bash git clone https://github.com/freqtrade/freqtrade.git - +cd freqtrade ``` Optionally checkout the master branch to get the latest stable release: @@ -171,22 +171,24 @@ Optionally checkout the master branch to get the latest stable release: git checkout master ``` -#### 4. Initialize the configuration - -```bash -cd freqtrade -cp config.json.example config.json -``` - -> *To edit the config please refer to [Bot Configuration](configuration.md).* - -#### 5. Install python dependencies +#### 4. Install python dependencies ``` bash python3 -m pip install --upgrade pip python3 -m pip install -e . ``` +#### 5. Initialize the configuration + +```bash +# Initialize the user_directory +freqtrade create-userdir --userdir user_data/ + +cp config.json.example config.json +``` + +> *To edit the config please refer to [Bot Configuration](configuration.md).* + #### 6. Run the Bot 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. @@ -227,7 +229,7 @@ If that is not available on your system, feel free to try the instructions below Make sure to use 64bit Windows and 64bit Python to avoid problems with backtesting or hyperopt due to the memory constraints 32bit applications have under Windows. !!! Hint - Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Conda section](#using-conda) in this document. + Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Conda section](#using-conda) in this document for more information. #### Clone the git repository diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 03aecd6ba..d0276579e 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -48,7 +48,7 @@ Future versions will require this to be set. 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) +**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py) file as reference.** !!! Note "Strategies and Backtesting" @@ -114,7 +114,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame ``` !!! Note "Want more indicator examples?" - Look into the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py). + Look into the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py). Then uncomment indicators you need. ### Strategy startup period @@ -478,7 +478,7 @@ Printing more than a few rows is also possible (simply use `print(dataframe)` i ### Where can i find a strategy template? The strategy template is located in the file -[user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py). +[user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py). ### Specify custom strategy location From e3cf6188a1d761963ad59cca4aa8f9c84833c68a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 16:04:44 +0100 Subject: [PATCH 10/76] Add first version of new-strategy generation from template --- freqtrade/configuration/arguments.py | 11 +- freqtrade/misc.py | 13 + freqtrade/templates/base_strategy.py.j2 | 306 ++++++++++++++++++++++++ freqtrade/utils.py | 23 +- 4 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 freqtrade/templates/base_strategy.py.j2 diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 149e28d2b..f48121032 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -39,6 +39,8 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] +ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy"] + ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange", "timeframes", "erase"] @@ -52,7 +54,7 @@ ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", NO_CONF_REQURIED = ["download-data", "list-timeframes", "list-markets", "list-pairs", "plot-dataframe", "plot-profit"] -NO_CONF_ALLOWED = ["create-userdir", "list-exchanges"] +NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"] class Arguments: @@ -117,6 +119,7 @@ class Arguments: from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge from freqtrade.utils import (start_create_userdir, start_download_data, start_list_exchanges, start_list_markets, + start_new_strategy, start_list_timeframes, start_trading) from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit @@ -158,6 +161,12 @@ class Arguments: create_userdir_cmd.set_defaults(func=start_create_userdir) self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd) + # add new-strategy subcommand + build_strategy_cmd = subparsers.add_parser('new-strategy', + help="Create new strategy") + build_strategy_cmd.set_defaults(func=start_new_strategy) + self._build_args(optionlist=ARGS_BUILD_STRATEGY, parser=build_strategy_cmd) + # Add list-exchanges subcommand list_exchanges_cmd = subparsers.add_parser( 'list-exchanges', diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 7682b5285..1745921d6 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -127,3 +127,16 @@ def round_dict(d, n): def plural(num, singular: str, plural: str = None) -> str: return singular if (num == 1 or num == -1) else plural or singular + 's' + + +def render_template(template: str, arguments: dict): + + from jinja2 import Environment, PackageLoader, select_autoescape + + env = Environment( + loader=PackageLoader('freqtrade', 'templates'), + autoescape=select_autoescape(['html', 'xml']) + ) + template = env.get_template(template) + + return template.render(**arguments) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 new file mode 100644 index 000000000..3fbd26997 --- /dev/null +++ b/freqtrade/templates/base_strategy.py.j2 @@ -0,0 +1,306 @@ + +# --- Do not remove these libs --- +from freqtrade.strategy.interface import IStrategy +from pandas import DataFrame +# -------------------------------- + +# Add your lib to import here +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +import numpy # noqa + + +# This class is a sample. Feel free to customize it. +class {{ strategy }}(IStrategy): + """ + This is a strategy template to get you started.. + More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md + + You can: + :return: a Dataframe with all mandatory indicators for the strategies + - Rename the class name (Do not forget to update class_name) + - Add any methods you want to build your strategy + - Add any lib you need to build your strategy + + You must keep: + - the lib in the section "Do not remove these libs" + - the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend, + populate_sell_trend, hyperopt_space, buy_strategy_generator + """ + # Strategy interface version - allow new iterations of the strategy interface. + # Check the documentation or the Sample strategy to get the latest version. + INTERFACE_VERSION = 2 + + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi". + minimal_roi = { + "60": 0.01, + "30": 0.02, + "0": 0.04 + } + + # Optimal stoploss designed for the strategy. + # This attribute will be overridden if the config file contains "stoploss". + stoploss = -0.10 + + # Trailing stoploss + trailing_stop = False + # trailing_stop_positive = 0.01 + # trailing_stop_positive_offset = 0.0 # Disabled / not configured + + # Optimal ticker interval for the strategy. + ticker_interval = '5m' + + # Run "populate_indicators()" only for new candle. + process_only_new_candles = False + + # These values can be overridden in the "ask_strategy" section in the config. + use_sell_signal = True + sell_profit_only = False + ignore_roi_if_buy_signal = False + + # Number of candles the strategy requires before producing valid signals + startup_candle_count: int = 20 + + # Optional order type mapping. + order_types = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'market', + 'stoploss_on_exchange': False + } + + # Optional order time in force. + order_time_in_force = { + 'buy': 'gtc', + 'sell': 'gtc' + } + + def informative_pairs(self): + """ + Define additional, informative pair/interval combinations to be cached from the exchange. + These pair/interval combinations are non-tradeable, unless they are part + of the whitelist as well. + For more information, please consult the documentation + :return: List of tuples in the format (pair, interval) + Sample: return [("ETH/USDT", "5m"), + ("BTC/USDT", "15m"), + ] + """ + return [] + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + :param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe() + :param metadata: Additional information, like the currently traded pair + :return: a Dataframe with all mandatory indicators for the strategies + """ + + # Momentum Indicators + # ------------------------------------ + + # RSI + dataframe['rsi'] = ta.RSI(dataframe) + + """ + # ADX + dataframe['adx'] = ta.ADX(dataframe) + + # Awesome oscillator + dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) + + # Commodity Channel Index: values Oversold:<-100, Overbought:>100 + dataframe['cci'] = ta.CCI(dataframe) + + # MACD + macd = ta.MACD(dataframe) + dataframe['macd'] = macd['macd'] + dataframe['macdsignal'] = macd['macdsignal'] + dataframe['macdhist'] = macd['macdhist'] + + # MFI + dataframe['mfi'] = ta.MFI(dataframe) + + # Minus Directional Indicator / Movement + dataframe['minus_dm'] = ta.MINUS_DM(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # Plus Directional Indicator / Movement + dataframe['plus_dm'] = ta.PLUS_DM(dataframe) + dataframe['plus_di'] = ta.PLUS_DI(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # ROC + dataframe['roc'] = ta.ROC(dataframe) + + # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) + rsi = 0.1 * (dataframe['rsi'] - 50) + dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) + + # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) + dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + + # Stoch + stoch = ta.STOCH(dataframe) + dataframe['slowd'] = stoch['slowd'] + dataframe['slowk'] = stoch['slowk'] + + # Stoch fast + stoch_fast = ta.STOCHF(dataframe) + dataframe['fastd'] = stoch_fast['fastd'] + dataframe['fastk'] = stoch_fast['fastk'] + + # Stoch RSI + stoch_rsi = ta.STOCHRSI(dataframe) + dataframe['fastd_rsi'] = stoch_rsi['fastd'] + dataframe['fastk_rsi'] = stoch_rsi['fastk'] + """ + + # Overlap Studies + # ------------------------------------ + + # Bollinger bands + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + dataframe['bb_middleband'] = bollinger['mid'] + dataframe['bb_upperband'] = bollinger['upper'] + + """ + # EMA - Exponential Moving Average + dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) + dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) + dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) + dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + + # SAR Parabol + dataframe['sar'] = ta.SAR(dataframe) + + # SMA - Simple Moving Average + dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) + """ + + # TEMA - Triple Exponential Moving Average + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + # Cycle Indicator + # ------------------------------------ + # Hilbert Transform Indicator - SineWave + hilbert = ta.HT_SINE(dataframe) + dataframe['htsine'] = hilbert['sine'] + dataframe['htleadsine'] = hilbert['leadsine'] + + # Pattern Recognition - Bullish candlestick patterns + # ------------------------------------ + """ + # Hammer: values [0, 100] + dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) + # Inverted Hammer: values [0, 100] + dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) + # Dragonfly Doji: values [0, 100] + dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) + # Piercing Line: values [0, 100] + dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] + # Morningstar: values [0, 100] + dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] + # Three White Soldiers: values [0, 100] + dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] + """ + + # Pattern Recognition - Bearish candlestick patterns + # ------------------------------------ + """ + # Hanging Man: values [0, 100] + dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) + # Shooting Star: values [0, 100] + dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) + # Gravestone Doji: values [0, 100] + dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) + # Dark Cloud Cover: values [0, 100] + dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) + # Evening Doji Star: values [0, 100] + dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) + # Evening Star: values [0, 100] + dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) + """ + + # Pattern Recognition - Bullish/Bearish candlestick patterns + # ------------------------------------ + """ + # Three Line Strike: values [0, -100, 100] + dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) + # Spinning Top: values [0, -100, 100] + dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] + # Engulfing: values [0, -100, 100] + dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] + # Harami: values [0, -100, 100] + dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] + # Three Outside Up/Down: values [0, -100, 100] + dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] + # Three Inside Up/Down: values [0, -100, 100] + dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] + """ + + # Chart type + # ------------------------------------ + """ + # Heikinashi stategy + heikinashi = qtpylib.heikinashi(dataframe) + dataframe['ha_open'] = heikinashi['open'] + dataframe['ha_close'] = heikinashi['close'] + dataframe['ha_high'] = heikinashi['high'] + dataframe['ha_low'] = heikinashi['low'] + """ + + # Retrieve best bid and best ask from the orderbook + # ------------------------------------ + """ + # first check if dataprovider is available + if self.dp: + if self.dp.runmode in ('live', 'dry_run'): + ob = self.dp.orderbook(metadata['pair'], 1) + dataframe['best_bid'] = ob['bids'][0][0] + dataframe['best_ask'] = ob['asks'][0][0] + """ + + return dataframe + + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame populated with indicators + :param metadata: Additional information, like the currently traded pair + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 + (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle + (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'buy'] = 1 + + return dataframe + + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame populated with indicators + :param metadata: Additional information, like the currently traded pair + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'sell'] = 1 + return dataframe diff --git a/freqtrade/utils.py b/freqtrade/utils.py index b9730da10..974d5a2c3 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -18,7 +18,7 @@ from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_trades_data) from freqtrade.exchange import (available_exchanges, ccxt_exchanges, market_is_active, symbol_is_pair) -from freqtrade.misc import plural +from freqtrade.misc import plural, render_template from freqtrade.resolvers import ExchangeResolver from freqtrade.state import RunMode @@ -89,6 +89,27 @@ def start_create_userdir(args: Dict[str, Any]) -> None: sys.exit(1) +def start_new_strategy(args: Dict[str, Any]) -> None: + + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + if "strategy" in args and args["strategy"]: + new_path = config['user_data_dir'] / "strategies" / (args["strategy"] + ".py") + + if new_path.exists(): + raise OperationalException(f"`{new_path}` already exists. " + "Please choose another Strategy Name.") + + strategy_text = render_template(template='base_strategy.py.j2', + arguments={"strategy": args["strategy"]}) + + logger.info(f"Writing strategy to `{new_path}`.") + new_path.write_text(strategy_text) + else: + logger.warning("`new-strategy` requires --strategy to be set.") + sys.exit(1) + + def start_download_data(args: Dict[str, Any]) -> None: """ Download data (former download_backtest_data.py script) From 98baae9456c7e895e39c7fe794d2ceeabd5aecb9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 16:09:30 +0100 Subject: [PATCH 11/76] Add jinja2 to requirements --- requirements-common.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements-common.txt b/requirements-common.txt index 63cf48eee..2c176e9c3 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -12,6 +12,7 @@ jsonschema==3.1.1 TA-Lib==0.4.17 tabulate==0.8.6 coinmarketcap==5.0.3 +jinja2==2.10.3 # find first, C search in arrays py_find_1st==1.1.4 diff --git a/setup.py b/setup.py index 50b8eee9c..3710bcdc0 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ setup(name='freqtrade', 'python-rapidjson', 'sdnotify', 'colorama', + 'jinja2', # from requirements.txt 'numpy', 'pandas', From e492d47621fe68bb2d1aae5e9dcc1f5e541c605b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 1 Nov 2019 19:50:42 +0100 Subject: [PATCH 12/76] Disallow usage of DefaultStrategy --- freqtrade/utils.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 974d5a2c3..2a6b0182c 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -1,4 +1,4 @@ -from freqtrade.loggers import setup_logging +import csv import logging import sys from collections import OrderedDict @@ -6,18 +6,20 @@ from pathlib import Path from typing import Any, Dict, List import arrow -import csv import rapidjson from tabulate import tabulate from freqtrade import OperationalException -from freqtrade.configuration import Configuration, TimeRange, remove_credentials -from freqtrade.configuration.directory_operations import create_userdata_dir, copy_sample_files +from freqtrade.configuration import (Configuration, TimeRange, + remove_credentials) +from freqtrade.configuration.directory_operations import (copy_sample_files, + create_userdata_dir) +from freqtrade.constants import DEFAULT_STRATEGY from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) -from freqtrade.exchange import (available_exchanges, ccxt_exchanges, market_is_active, - symbol_is_pair) +from freqtrade.exchange import (available_exchanges, ccxt_exchanges, + market_is_active, symbol_is_pair) from freqtrade.misc import plural, render_template from freqtrade.resolvers import ExchangeResolver from freqtrade.state import RunMode @@ -94,6 +96,9 @@ def start_new_strategy(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if "strategy" in args and args["strategy"]: + if args["strategy"] == DEFAULT_STRATEGY: + raise OperationalException("DefaultStrategy is not allowed as name.") + new_path = config['user_data_dir'] / "strategies" / (args["strategy"] + ".py") if new_path.exists(): From 8c2ff2f46e3d38a118bcfe57363cf3569c5618a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Nov 2019 10:42:17 +0100 Subject: [PATCH 13/76] Add template for new-hyperopt command --- freqtrade/configuration/arguments.py | 12 +- freqtrade/templates/base_hyperopt.py.j2 | 154 ++++++++++++++++++++++++ freqtrade/utils.py | 26 +++- 3 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 freqtrade/templates/base_hyperopt.py.j2 diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index f48121032..4ba2d04c4 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -41,6 +41,8 @@ ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy"] +ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt"] + ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange", "timeframes", "erase"] @@ -54,7 +56,7 @@ ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", NO_CONF_REQURIED = ["download-data", "list-timeframes", "list-markets", "list-pairs", "plot-dataframe", "plot-profit"] -NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"] +NO_CONF_ALLOWED = ["create-userdir", "list-exchanges","new-hyperopt", "new-strategy"] class Arguments: @@ -119,7 +121,7 @@ class Arguments: from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge from freqtrade.utils import (start_create_userdir, start_download_data, start_list_exchanges, start_list_markets, - start_new_strategy, + start_new_hyperopt, start_new_strategy, start_list_timeframes, start_trading) from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit @@ -167,6 +169,12 @@ class Arguments: build_strategy_cmd.set_defaults(func=start_new_strategy) self._build_args(optionlist=ARGS_BUILD_STRATEGY, parser=build_strategy_cmd) + # add new-hyperopt subcommand + build_hyperopt_cmd = subparsers.add_parser('new-hyperopt', + help="Create new hyperopt") + build_hyperopt_cmd.set_defaults(func=start_new_hyperopt) + self._build_args(optionlist=ARGS_BUILD_HYPEROPT, parser=build_hyperopt_cmd) + # Add list-exchanges subcommand list_exchanges_cmd = subparsers.add_parser( 'list-exchanges', diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 new file mode 100644 index 000000000..ad30cfe55 --- /dev/null +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -0,0 +1,154 @@ +# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement + +from functools import reduce +from typing import Any, Callable, Dict, List + +import numpy as np # noqa +import talib.abstract as ta +from pandas import DataFrame +from skopt.space import Categorical, Dimension, Integer, Real # noqa + +import freqtrade.vendor.qtpylib.indicators as qtpylib +from freqtrade.optimize.hyperopt_interface import IHyperOpt + + +class {{ hyperopt }}(IHyperOpt): + """ + This is a Hyperopt template to get you started. + + More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md + + You should: + - Add any lib you need to build your hyperopt. + + You must keep: + - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. + + The roi_space, generate_roi_table, stoploss_space methods are no longer required to be + copied in every custom hyperopt. However, you may override them if you need the + 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. + Sample implementation of these methods can be found in + https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py + """ + + @staticmethod + def buy_strategy_generator(params: Dict[str, Any]) -> Callable: + """ + Define the buy strategy parameters to be used by Hyperopt. + """ + def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Buy strategy Hyperopt will build and use. + """ + conditions = [] + + # GUARDS AND TRENDS + if 'mfi-enabled' in params and params['mfi-enabled']: + conditions.append(dataframe['mfi'] < params['mfi-value']) + if 'fastd-enabled' in params and params['fastd-enabled']: + conditions.append(dataframe['fastd'] < params['fastd-value']) + if 'adx-enabled' in params and params['adx-enabled']: + conditions.append(dataframe['adx'] > params['adx-value']) + if 'rsi-enabled' in params and params['rsi-enabled']: + conditions.append(dataframe['rsi'] < params['rsi-value']) + + # TRIGGERS + if 'trigger' in params: + if params['trigger'] == 'bb_lower': + conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + if params['trigger'] == 'macd_cross_signal': + conditions.append(qtpylib.crossed_above( + dataframe['macd'], dataframe['macdsignal'] + )) + if params['trigger'] == 'sar_reversal': + conditions.append(qtpylib.crossed_above( + dataframe['close'], dataframe['sar'] + )) + + if conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'buy'] = 1 + + return dataframe + + return populate_buy_trend + + @staticmethod + def indicator_space() -> List[Dimension]: + """ + Define your Hyperopt space for searching buy strategy parameters. + """ + return [ + Integer(10, 25, name='mfi-value'), + Integer(15, 45, name='fastd-value'), + Integer(20, 50, name='adx-value'), + Integer(20, 40, name='rsi-value'), + Categorical([True, False], name='mfi-enabled'), + Categorical([True, False], name='fastd-enabled'), + Categorical([True, False], name='adx-enabled'), + Categorical([True, False], name='rsi-enabled'), + Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') + ] + + @staticmethod + def sell_strategy_generator(params: Dict[str, Any]) -> Callable: + """ + Define the sell strategy parameters to be used by Hyperopt. + """ + def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Sell strategy Hyperopt will build and use. + """ + conditions = [] + + # GUARDS AND TRENDS + if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: + conditions.append(dataframe['mfi'] > params['sell-mfi-value']) + if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']: + conditions.append(dataframe['fastd'] > params['sell-fastd-value']) + if 'sell-adx-enabled' in params and params['sell-adx-enabled']: + conditions.append(dataframe['adx'] < params['sell-adx-value']) + if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']: + conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + + # TRIGGERS + if 'sell-trigger' in params: + if params['sell-trigger'] == 'sell-bb_upper': + conditions.append(dataframe['close'] > dataframe['bb_upperband']) + if params['sell-trigger'] == 'sell-macd_cross_signal': + conditions.append(qtpylib.crossed_above( + dataframe['macdsignal'], dataframe['macd'] + )) + if params['sell-trigger'] == 'sell-sar_reversal': + conditions.append(qtpylib.crossed_above( + dataframe['sar'], dataframe['close'] + )) + + if conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'sell'] = 1 + + return dataframe + + return populate_sell_trend + + @staticmethod + def sell_indicator_space() -> List[Dimension]: + """ + Define your Hyperopt space for searching sell strategy parameters. + """ + return [ + Integer(75, 100, name='sell-mfi-value'), + Integer(50, 100, name='sell-fastd-value'), + Integer(50, 100, name='sell-adx-value'), + Integer(60, 100, name='sell-rsi-value'), + Categorical([True, False], name='sell-mfi-enabled'), + Categorical([True, False], name='sell-fastd-enabled'), + Categorical([True, False], name='sell-adx-enabled'), + Categorical([True, False], name='sell-rsi-enabled'), + Categorical(['sell-bb_upper', + 'sell-macd_cross_signal', + 'sell-sar_reversal'], name='sell-trigger') + ] diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 2a6b0182c..dd61cfcab 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -14,7 +14,7 @@ from freqtrade.configuration import (Configuration, TimeRange, remove_credentials) from freqtrade.configuration.directory_operations import (copy_sample_files, create_userdata_dir) -from freqtrade.constants import DEFAULT_STRATEGY +from freqtrade.constants import DEFAULT_HYPEROPT, DEFAULT_STRATEGY from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) @@ -115,6 +115,30 @@ def start_new_strategy(args: Dict[str, Any]) -> None: sys.exit(1) +def start_new_hyperopt(args: Dict[str, Any]) -> None: + + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + if "hyperopt" in args and args["hyperopt"]: + if args["hyperopt"] == DEFAULT_HYPEROPT: + raise OperationalException("DefaultHyperOpt is not allowed as name.") + + new_path = config['user_data_dir'] / "hyperopts" / (args["hyperopt"] + ".py") + + if new_path.exists(): + raise OperationalException(f"`{new_path}` already exists. " + "Please choose another Strategy Name.") + + strategy_text = render_template(template='base_hyperopt.py.j2', + arguments={"hyperopt": args["hyperopt"]}) + + logger.info(f"Writing hyperopt to `{new_path}`.") + new_path.write_text(strategy_text) + else: + logger.warning("`new-hyperopt` requires --hyperopt to be set.") + sys.exit(1) + + def start_download_data(args: Dict[str, Any]) -> None: """ Download data (former download_backtest_data.py script) From 8a1d02e185119ff11fd5b5031ced8f331acab9b6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 2 Nov 2019 15:34:09 +0100 Subject: [PATCH 14/76] Update numpy imports in sample strategies --- freqtrade/templates/base_strategy.py.j2 | 6 +++--- freqtrade/templates/sample_strategy.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 3fbd26997..312aa0f27 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -2,15 +2,15 @@ # --- Do not remove these libs --- from freqtrade.strategy.interface import IStrategy from pandas import DataFrame +import pandas as pd # -------------------------------- # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib -import numpy # noqa +import numpy as np # noqa -# This class is a sample. Feel free to customize it. class {{ strategy }}(IStrategy): """ This is a strategy template to get you started.. @@ -140,7 +140,7 @@ class {{ strategy }}(IStrategy): # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) rsi = 0.1 * (dataframe['rsi'] - 50) - dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) + dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 77a2d261a..d62e6120c 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -7,7 +7,8 @@ from pandas import DataFrame # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib -import numpy # noqa +import pandas as pd # noqa +import numpy as np # noqa # This class is a sample. Feel free to customize it. @@ -147,7 +148,7 @@ class SampleStrategy(IStrategy): # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) rsi = 0.1 * (dataframe['rsi'] - 50) - dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) + dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) From b36a1d3260d66ee21032fe19ec9aaf1e1ff27e14 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 13:31:07 +0100 Subject: [PATCH 15/76] test new_stratgy --- tests/test_utils.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 88c9af35d..902d56082 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,8 +9,8 @@ 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_trading) -from tests.conftest import get_args, log_has, patch_exchange + start_new_strategy, start_trading) +from tests.conftest import get_args, log_has, log_has_re, patch_exchange def test_setup_utils_configuration(): @@ -455,6 +455,32 @@ def test_create_datadir(caplog, mocker): assert len(caplog.record_tuples) == 0 +def test_start_new_strategy(mocker, caplog): + wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) + args = [ + "new-strategy", + "--strategy", + "CoolNewStrategy" + ] + start_new_strategy(get_args(args)) + + assert wt_mock.call_count == 1 + assert "CoolNewStrategy" in wt_mock.call_args_list[0][0][0] + assert log_has_re("Writing strategy to .*", caplog) + + + +def test_start_new_strategy_DefaultStrat(mocker, caplog): + args = [ + "new-strategy", + "--strategy", + "DefaultStrategy" + ] + with pytest.raises(OperationalException, + match=r"DefaultStrategy is not allowed as name\."): + start_new_strategy(get_args(args)) + + def test_download_data_keyboardInterrupt(mocker, caplog, markets): dl_mock = mocker.patch('freqtrade.utils.refresh_backtest_ohlcv_data', MagicMock(side_effect=KeyboardInterrupt)) From 65489c894d9cd2dc9383b8cff57e75a2ecf8a2de Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2019 13:33:37 +0100 Subject: [PATCH 16/76] Add no-arg test --- freqtrade/configuration/arguments.py | 2 +- freqtrade/utils.py | 6 ++---- tests/test_utils.py | 10 +++++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 4ba2d04c4..5cc56a8bc 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -56,7 +56,7 @@ ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", NO_CONF_REQURIED = ["download-data", "list-timeframes", "list-markets", "list-pairs", "plot-dataframe", "plot-profit"] -NO_CONF_ALLOWED = ["create-userdir", "list-exchanges","new-hyperopt", "new-strategy"] +NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"] class Arguments: diff --git a/freqtrade/utils.py b/freqtrade/utils.py index dd61cfcab..314ec4f36 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -111,8 +111,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None: logger.info(f"Writing strategy to `{new_path}`.") new_path.write_text(strategy_text) else: - logger.warning("`new-strategy` requires --strategy to be set.") - sys.exit(1) + raise OperationalException("`new-strategy` requires --strategy to be set.") def start_new_hyperopt(args: Dict[str, Any]) -> None: @@ -135,8 +134,7 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: logger.info(f"Writing hyperopt to `{new_path}`.") new_path.write_text(strategy_text) else: - logger.warning("`new-hyperopt` requires --hyperopt to be set.") - sys.exit(1) + raise OperationalException("`new-hyperopt` requires --hyperopt to be set.") def start_download_data(args: Dict[str, Any]) -> None: diff --git a/tests/test_utils.py b/tests/test_utils.py index 902d56082..c64050f38 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -469,7 +469,6 @@ def test_start_new_strategy(mocker, caplog): assert log_has_re("Writing strategy to .*", caplog) - def test_start_new_strategy_DefaultStrat(mocker, caplog): args = [ "new-strategy", @@ -481,6 +480,15 @@ def test_start_new_strategy_DefaultStrat(mocker, caplog): start_new_strategy(get_args(args)) +def test_start_new_strategy_no_arg(mocker, caplog): + args = [ + "new-strategy", + ] + with pytest.raises(OperationalException, + match="`new-strategy` requires --strategy to be set."): + start_new_strategy(get_args(args)) + + def test_download_data_keyboardInterrupt(mocker, caplog, markets): dl_mock = mocker.patch('freqtrade.utils.refresh_backtest_ohlcv_data', MagicMock(side_effect=KeyboardInterrupt)) From 79891671e99eec20dbcd172ddaa242edd92be72b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 15 Nov 2019 06:49:58 +0100 Subject: [PATCH 17/76] Adapt after rebase --- freqtrade/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 314ec4f36..cdbbc8163 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -14,7 +14,6 @@ from freqtrade.configuration import (Configuration, TimeRange, remove_credentials) from freqtrade.configuration.directory_operations import (copy_sample_files, create_userdata_dir) -from freqtrade.constants import DEFAULT_HYPEROPT, DEFAULT_STRATEGY from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) @@ -96,7 +95,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if "strategy" in args and args["strategy"]: - if args["strategy"] == DEFAULT_STRATEGY: + if args["strategy"] == "DefaultStrategy": raise OperationalException("DefaultStrategy is not allowed as name.") new_path = config['user_data_dir'] / "strategies" / (args["strategy"] + ".py") @@ -119,7 +118,7 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) if "hyperopt" in args and args["hyperopt"]: - if args["hyperopt"] == DEFAULT_HYPEROPT: + if args["hyperopt"] == "DefaultHyperopt": raise OperationalException("DefaultHyperOpt is not allowed as name.") new_path = config['user_data_dir'] / "hyperopts" / (args["hyperopt"] + ".py") From 37f813943228d557aae04481a12d41a593d928b3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Nov 2019 14:47:44 +0100 Subject: [PATCH 18/76] Small stylistic fixes --- freqtrade/templates/base_hyperopt.py.j2 | 10 +++++++--- freqtrade/templates/base_strategy.py.j2 | 20 ++++++++++---------- tests/strategy/test_strategy.py | 2 +- tests/test_utils.py | 2 ++ 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index ad30cfe55..75aedbf86 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -3,14 +3,18 @@ from functools import reduce from typing import Any, Callable, Dict, List -import numpy as np # noqa -import talib.abstract as ta from pandas import DataFrame +import pandas as pd # noqa +import numpy as np # noqa from skopt.space import Categorical, Dimension, Integer, Real # noqa -import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.optimize.hyperopt_interface import IHyperOpt +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib + class {{ hyperopt }}(IHyperOpt): """ diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 312aa0f27..46c118383 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -2,18 +2,18 @@ # --- Do not remove these libs --- from freqtrade.strategy.interface import IStrategy from pandas import DataFrame -import pandas as pd +import pandas as pd # noqa +import numpy as np # noqa # -------------------------------- # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib -import numpy as np # noqa class {{ strategy }}(IStrategy): """ - This is a strategy template to get you started.. + This is a strategy template to get you started. More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md You can: @@ -107,16 +107,15 @@ class {{ strategy }}(IStrategy): # RSI dataframe['rsi'] = ta.RSI(dataframe) - """ # ADX dataframe['adx'] = ta.ADX(dataframe) - + """ # Awesome oscillator dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) # Commodity Channel Index: values Oversold:<-100, Overbought:>100 dataframe['cci'] = ta.CCI(dataframe) - + """ # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] @@ -126,6 +125,7 @@ class {{ strategy }}(IStrategy): # MFI dataframe['mfi'] = ta.MFI(dataframe) + """ # Minus Directional Indicator / Movement dataframe['minus_dm'] = ta.MINUS_DM(dataframe) dataframe['minus_di'] = ta.MINUS_DI(dataframe) @@ -149,12 +149,13 @@ class {{ strategy }}(IStrategy): stoch = ta.STOCH(dataframe) dataframe['slowd'] = stoch['slowd'] dataframe['slowk'] = stoch['slowk'] - + """ # Stoch fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] + """ # Stoch RSI stoch_rsi = ta.STOCHRSI(dataframe) dataframe['fastd_rsi'] = stoch_rsi['fastd'] @@ -178,12 +179,11 @@ class {{ strategy }}(IStrategy): dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) - # SAR Parabol - dataframe['sar'] = ta.SAR(dataframe) - # SMA - Simple Moving Average dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) """ + # SAR Parabol + dataframe['sar'] = ta.SAR(dataframe) # TEMA - Triple Exponential Moving Average dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) diff --git a/tests/strategy/test_strategy.py b/tests/strategy/test_strategy.py index 2b84bc6ee..963d36c76 100644 --- a/tests/strategy/test_strategy.py +++ b/tests/strategy/test_strategy.py @@ -56,7 +56,7 @@ def test_load_strategy_base64(result, caplog, default_conf): def test_load_strategy_invalid_directory(result, caplog, default_conf): - default_conf['strategy'] = 'SampleStrategy' + default_conf['strategy'] = 'DefaultStrategy' resolver = StrategyResolver(default_conf) extra_dir = Path.cwd() / 'some/path' resolver._load_strategy('DefaultStrategy', config=default_conf, extra_dir=extra_dir) diff --git a/tests/test_utils.py b/tests/test_utils.py index c64050f38..ce93c328d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -457,6 +457,8 @@ def test_create_datadir(caplog, mocker): def test_start_new_strategy(mocker, caplog): wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + args = [ "new-strategy", "--strategy", From 03cdfe8cae6812485073ce2d3ceff97d52593ef9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Nov 2019 15:52:32 +0100 Subject: [PATCH 19/76] Add tests for new-hyperopt --- freqtrade/utils.py | 2 +- tests/test_utils.py | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index cdbbc8163..7d3bd69ed 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -119,7 +119,7 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: if "hyperopt" in args and args["hyperopt"]: if args["hyperopt"] == "DefaultHyperopt": - raise OperationalException("DefaultHyperOpt is not allowed as name.") + raise OperationalException("DefaultHyperopt is not allowed as name.") new_path = config['user_data_dir'] / "hyperopts" / (args["hyperopt"] + ".py") diff --git a/tests/test_utils.py b/tests/test_utils.py index ce93c328d..1258c939c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,7 +9,8 @@ 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_new_strategy, start_trading) + start_new_hyperopt, start_new_strategy, + start_trading) from tests.conftest import get_args, log_has, log_has_re, patch_exchange @@ -491,6 +492,42 @@ def test_start_new_strategy_no_arg(mocker, caplog): start_new_strategy(get_args(args)) +def test_start_new_hyperopt(mocker, caplog): + wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) + mocker.patch.object(Path, "exists", MagicMock(return_value=False)) + + args = [ + "new-hyperopt", + "--hyperopt", + "CoolNewhyperopt" + ] + start_new_hyperopt(get_args(args)) + + assert wt_mock.call_count == 1 + assert "CoolNewhyperopt" in wt_mock.call_args_list[0][0][0] + assert log_has_re("Writing hyperopt to .*", caplog) + + +def test_start_new_hyperopt_DefaultHyperopt(mocker, caplog): + args = [ + "new-hyperopt", + "--hyperopt", + "DefaultHyperopt" + ] + with pytest.raises(OperationalException, + match=r"DefaultHyperopt is not allowed as name\."): + start_new_hyperopt(get_args(args)) + + +def test_start_new_hyperopt_no_arg(mocker, caplog): + args = [ + "new-hyperopt", + ] + with pytest.raises(OperationalException, + match="`new-hyperopt` requires --hyperopt to be set."): + start_new_hyperopt(get_args(args)) + + def test_download_data_keyboardInterrupt(mocker, caplog, markets): dl_mock = mocker.patch('freqtrade.utils.refresh_backtest_ohlcv_data', MagicMock(side_effect=KeyboardInterrupt)) From cbb187e9b989a001f844d4fecf795f7084ce8e19 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Nov 2019 22:00:50 +0100 Subject: [PATCH 20/76] Use constant for Strategy and hyperopt userdirpaths --- freqtrade/constants.py | 13 ++++++++----- freqtrade/misc.py | 4 ++-- freqtrade/resolvers/hyperopt_resolver.py | 6 +++--- freqtrade/resolvers/strategy_resolver.py | 3 ++- freqtrade/templates/sample_hyperopt.py | 2 +- freqtrade/utils.py | 9 +++++---- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 96109bc94..bf5d822c6 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -22,12 +22,15 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'P DRY_RUN_WALLET = 999.9 MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons -# Soure files with destination directories +USERPATH_HYPEROPTS = 'hyperopts' +USERPATH_STRATEGY = 'strategies' + +# Soure files with destination directories within user-directory USER_DATA_FILES = { - 'sample_strategy.py': 'strategies', - 'sample_hyperopt_advanced.py': 'hyperopts', - 'sample_hyperopt_loss.py': 'hyperopts', - 'sample_hyperopt.py': 'hyperopts', + 'sample_strategy.py': USERPATH_STRATEGY, + 'sample_hyperopt_advanced.py': USERPATH_HYPEROPTS, + 'sample_hyperopt_loss.py': USERPATH_HYPEROPTS, + 'sample_hyperopt.py': USERPATH_HYPEROPTS, 'strategy_analysis_example.ipynb': 'notebooks', } diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 1745921d6..6497a4727 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -129,7 +129,7 @@ def plural(num, singular: str, plural: str = None) -> str: return singular if (num == 1 or num == -1) else plural or singular + 's' -def render_template(template: str, arguments: dict): +def render_template(templatefile: str, arguments: dict): from jinja2 import Environment, PackageLoader, select_autoescape @@ -137,6 +137,6 @@ def render_template(template: str, arguments: dict): loader=PackageLoader('freqtrade', 'templates'), autoescape=select_autoescape(['html', 'xml']) ) - template = env.get_template(template) + template = env.get_template(templatefile) return template.render(**arguments) diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index df1ff182c..05efa1164 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Optional, Dict from freqtrade import OperationalException -from freqtrade.constants import DEFAULT_HYPEROPT_LOSS +from freqtrade.constants import DEFAULT_HYPEROPT_LOSS, USERPATH_HYPEROPTS from freqtrade.optimize.hyperopt_interface import IHyperOpt from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.resolvers import IResolver @@ -58,7 +58,7 @@ class HyperOptResolver(IResolver): current_path = Path(__file__).parent.parent.joinpath('optimize').resolve() abs_paths = self.build_search_paths(config, current_path=current_path, - user_subdir='hyperopts', extra_dir=extra_dir) + user_subdir=USERPATH_HYPEROPTS, extra_dir=extra_dir) hyperopt = self._load_object(paths=abs_paths, object_type=IHyperOpt, object_name=hyperopt_name, kwargs={'config': config}) @@ -110,7 +110,7 @@ class HyperOptLossResolver(IResolver): current_path = Path(__file__).parent.parent.joinpath('optimize').resolve() abs_paths = self.build_search_paths(config, current_path=current_path, - user_subdir='hyperopts', extra_dir=extra_dir) + user_subdir=USERPATH_HYPEROPTS, extra_dir=extra_dir) hyperoptloss = self._load_object(paths=abs_paths, object_type=IHyperOptLoss, object_name=hyper_loss_name) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 102816981..9a76b9b74 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -129,7 +129,8 @@ class StrategyResolver(IResolver): current_path = Path(__file__).parent.parent.joinpath('strategy').resolve() abs_paths = self.build_search_paths(config, current_path=current_path, - user_subdir='strategies', extra_dir=extra_dir) + user_subdir=constants.USERPATH_STRATEGY, + extra_dir=extra_dir) if ":" in strategy_name: logger.info("loading base64 encoded strategy") diff --git a/freqtrade/templates/sample_hyperopt.py b/freqtrade/templates/sample_hyperopt.py index 3be05f121..77afb2b98 100644 --- a/freqtrade/templates/sample_hyperopt.py +++ b/freqtrade/templates/sample_hyperopt.py @@ -4,7 +4,7 @@ from functools import reduce from typing import Any, Callable, Dict, List import numpy as np # noqa -import talib.abstract as ta +import talib.abstract as ta # noqa from pandas import DataFrame from skopt.space import Categorical, Dimension, Integer, Real # noqa diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 7d3bd69ed..3b37c6895 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -14,6 +14,7 @@ from freqtrade.configuration import (Configuration, TimeRange, remove_credentials) from freqtrade.configuration.directory_operations import (copy_sample_files, create_userdata_dir) +from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGY from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, refresh_backtest_trades_data) @@ -98,13 +99,13 @@ def start_new_strategy(args: Dict[str, Any]) -> None: if args["strategy"] == "DefaultStrategy": raise OperationalException("DefaultStrategy is not allowed as name.") - new_path = config['user_data_dir'] / "strategies" / (args["strategy"] + ".py") + new_path = config['user_data_dir'] / USERPATH_STRATEGY / (args["strategy"] + ".py") if new_path.exists(): raise OperationalException(f"`{new_path}` already exists. " "Please choose another Strategy Name.") - strategy_text = render_template(template='base_strategy.py.j2', + strategy_text = render_template(templatefile='base_strategy.py.j2', arguments={"strategy": args["strategy"]}) logger.info(f"Writing strategy to `{new_path}`.") @@ -121,13 +122,13 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: if args["hyperopt"] == "DefaultHyperopt": raise OperationalException("DefaultHyperopt is not allowed as name.") - new_path = config['user_data_dir'] / "hyperopts" / (args["hyperopt"] + ".py") + new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args["hyperopt"] + ".py") if new_path.exists(): raise OperationalException(f"`{new_path}` already exists. " "Please choose another Strategy Name.") - strategy_text = render_template(template='base_hyperopt.py.j2', + strategy_text = render_template(templatefile='base_hyperopt.py.j2', arguments={"hyperopt": args["hyperopt"]}) logger.info(f"Writing hyperopt to `{new_path}`.") From ed04f7f39d2d41f19a40f9b378d8b5350c70b053 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 09:57:06 +0100 Subject: [PATCH 21/76] Create userdir and backtest SampleStrategy --- .github/workflows/ci.yml | 8 ++++++-- .travis.yml | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8932cf07..2e4ca87f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,11 +78,13 @@ jobs: - name: Backtesting run: | cp config.json.example config.json - freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy + freqtrade create-userdir --userdir user_data + freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy - name: Hyperopt run: | cp config.json.example config.json + freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt - name: Flake8 @@ -139,11 +141,13 @@ jobs: - name: Backtesting run: | cp config.json.example config.json - freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy + freqtrade create-userdir --userdir user_data + freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy - name: Hyperopt run: | cp config.json.example config.json + freqtrade create-userdir --userdir user_data freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt - name: Flake8 diff --git a/.travis.yml b/.travis.yml index 6073e1cce..39e914fa4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,10 +28,12 @@ jobs: name: pytest - script: - cp config.json.example config.json - - freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy + freqtrade create-userdir --userdir user_data + - freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy name: backtest - script: - cp config.json.example config.json + - freqtrade create-userdir --userdir user_data - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt name: hyperopt - script: flake8 From 671b98ecad17ed952c038dbc2d6b43de6d8f2514 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 10:11:58 +0100 Subject: [PATCH 22/76] Fix windows test --- tests/test_directory_operations.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_directory_operations.py b/tests/test_directory_operations.py index c354b40b0..db41e2da2 100644 --- a/tests/test_directory_operations.py +++ b/tests/test_directory_operations.py @@ -57,11 +57,16 @@ def test_copy_sample_files(mocker, default_conf, caplog) -> None: copy_sample_files(Path('/tmp/bar')) assert copymock.call_count == 5 - assert copymock.call_args_list[0][0][1] == '/tmp/bar/strategies/sample_strategy.py' - assert copymock.call_args_list[1][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_advanced.py' - assert copymock.call_args_list[2][0][1] == '/tmp/bar/hyperopts/sample_hyperopt_loss.py' - assert copymock.call_args_list[3][0][1] == '/tmp/bar/hyperopts/sample_hyperopt.py' - assert copymock.call_args_list[4][0][1] == '/tmp/bar/notebooks/strategy_analysis_example.ipynb' + assert copymock.call_args_list[0][0][1] == str( + Path('/tmp/bar') / 'strategies/sample_strategy.py') + assert copymock.call_args_list[1][0][1] == str( + Path('/tmp/bar') / 'hyperopts/sample_hyperopt_advanced.py') + assert copymock.call_args_list[2][0][1] == str( + Path('/tmp/bar') / 'hyperopts/sample_hyperopt_loss.py') + assert copymock.call_args_list[3][0][1] == str( + Path('/tmp/bar') / 'hyperopts/sample_hyperopt.py') + assert copymock.call_args_list[4][0][1] == str( + Path('/tmp/bar') / 'notebooks/strategy_analysis_example.ipynb') def test_copy_sample_files_errors(mocker, default_conf, caplog) -> None: From f7322358cf045621ee5cdde2b712af0248606503 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Nov 2019 10:31:21 +0100 Subject: [PATCH 23/76] Update documentation --- .travis.yml | 2 +- docs/hyperopt.md | 3 ++ docs/strategy-customization.md | 22 +++++++----- docs/utils.md | 64 ++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39e914fa4..ec688a1f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ jobs: name: pytest - script: - cp config.json.example config.json - freqtrade create-userdir --userdir user_data + - freqtrade create-userdir --userdir user_data - freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy name: backtest - script: diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 8a750ef43..5a3ae7e3a 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -19,6 +19,9 @@ the sample hyperopt file located in [user_data/hyperopts/](https://github.com/fr Configuring hyperopt is similar to writing your own strategy, and many tasks will be similar and a lot of code can be copied across from the strategy. +The simplest way to get started is to use `freqtrade new-hyperopt --hyperopt AwesomeHyperopt`. +This will create a new hyperopt file from a template, which will be located under `user_data/hyperopts/AwesomeHyperopt.py`. + ### Checklist on all tasks / possibilities in hyperopt Depending on the space you want to optimize, only some of the below are required: diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index d0276579e..352389d5e 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -7,24 +7,28 @@ indicators. This is very simple. Copy paste your strategy file into the directory `user_data/strategies`. -Let assume you have a class called `AwesomeStrategy` in the file `awesome-strategy.py`: +Let assume you have a class called `AwesomeStrategy` in the file `AwesomeStrategy.py`: -1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py` +1. Move your file into `user_data/strategies` (you should have `user_data/strategies/AwesomeStrategy.py` 2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name) ```bash freqtrade trade --strategy AwesomeStrategy ``` -## Change your strategy +## Develop your own strategy -The bot includes a default strategy file. However, we recommend you to -use your own file to not have to lose your parameters every time the default -strategy file will be updated on Github. Put your custom strategy file -into the directory `user_data/strategies`. +The bot includes a default strategy file. +Also, several other strategies are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). -Best copy the test-strategy and modify this copy to avoid having bot-updates override your changes. -`cp user_data/strategies/sample_strategy.py user_data/strategies/awesome-strategy.py` +You will however most likely have your own idea for a strategy. +This Document intends to help you develop one for yourself. + +To get started, use `freqtrade new-strategy --strategy AwesomeStrategy`. +This will create a new strategy file from a template, which will be located under `user_data/strategies/AwesomeStrategy.py`. + +!!! Note + This is just a template file, which will most likely not be profitable out of the box. ### Anatomy of a strategy diff --git a/docs/utils.md b/docs/utils.md index d9baee32c..26d354206 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -36,6 +36,70 @@ optional arguments: └── sample_strategy.py ``` +## Create new strategy + +Creates a new strategy from a template similar to SampleStrategy. +The file will be named inline with your class name, and will not overwrite existing files. + +Results will be located in `user_data/strategies/.py`. + +### Sample usage of new-strategy + +```bash +freqtrade new-strategy --strategy AwesomeStrategy +``` + +With custom user directory + +```bash +freqtrade new-strategy --userdir ~/.freqtrade/ --strategy AwesomeStrategy +``` + +### new-strategy complete options + +``` output +usage: freqtrade new-strategy [-h] [--userdir PATH] [-s NAME] + +optional arguments: + -h, --help show this help message and exit + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + -s NAME, --strategy NAME + Specify strategy class name which will be used by the + bot. +``` + +## Create new hyperopt + +Creates a new hyperopt from a template similar to SampleHyperopt. +The file will be named inline with your class name, and will not overwrite existing files. + +Results will be located in `user_data/hyperopts/.py`. + +### Sample usage of new-hyperopt + +```bash +freqtrade new-hyperopt --hyperopt AwesomeHyperopt +``` + +With custom user directory + +```bash +freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt +``` + +### new-hyperopt complete options + +``` output +usage: freqtrade new-hyperopt [-h] [--userdir PATH] [--hyperopt NAME] + +optional arguments: + -h, --help show this help message and exit + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + --hyperopt NAME Specify hyperopt class name which will be used by the + bot. +``` ## List Exchanges From be4a4180ae9fcaca3d95db72d561c98d0bcd954d Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Nov 2019 06:40:30 +0100 Subject: [PATCH 24/76] Use single line comments for samples --- freqtrade/templates/base_strategy.py.j2 | 186 +++++++++++------------ freqtrade/templates/sample_strategy.py | 189 +++++++++++------------- 2 files changed, 178 insertions(+), 197 deletions(-) diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 46c118383..174c801ee 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -109,58 +109,61 @@ class {{ strategy }}(IStrategy): # ADX dataframe['adx'] = ta.ADX(dataframe) - """ - # Awesome oscillator - dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) - # Commodity Channel Index: values Oversold:<-100, Overbought:>100 - dataframe['cci'] = ta.CCI(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) + + # # Commodity Channel Index: values Oversold:<-100, Overbought:>100 + # dataframe['cci'] = ta.CCI(dataframe) + # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] - # MFI - dataframe['mfi'] = ta.MFI(dataframe) + # # MFI + # dataframe['mfi'] = ta.MFI(dataframe) - """ - # Minus Directional Indicator / Movement - dataframe['minus_dm'] = ta.MINUS_DM(dataframe) - dataframe['minus_di'] = ta.MINUS_DI(dataframe) + # # Minus Directional Indicator / Movement + # dataframe['minus_dm'] = ta.MINUS_DM(dataframe) + # dataframe['minus_di'] = ta.MINUS_DI(dataframe) - # Plus Directional Indicator / Movement - dataframe['plus_dm'] = ta.PLUS_DM(dataframe) - dataframe['plus_di'] = ta.PLUS_DI(dataframe) - dataframe['minus_di'] = ta.MINUS_DI(dataframe) + # # Plus Directional Indicator / Movement + # dataframe['plus_dm'] = ta.PLUS_DM(dataframe) + # dataframe['plus_di'] = ta.PLUS_DI(dataframe) + # dataframe['minus_di'] = ta.MINUS_DI(dataframe) - # ROC - dataframe['roc'] = ta.ROC(dataframe) + # # ROC + # dataframe['roc'] = ta.ROC(dataframe) - # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) - rsi = 0.1 * (dataframe['rsi'] - 50) - dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) + # # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) + # rsi = 0.1 * (dataframe['rsi'] - 50) + # dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) - # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) - dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + # # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) + # dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + + # # Stoch + # stoch = ta.STOCH(dataframe) + # dataframe['slowd'] = stoch['slowd'] + # dataframe['slowk'] = stoch['slowk'] - # Stoch - stoch = ta.STOCH(dataframe) - dataframe['slowd'] = stoch['slowd'] - dataframe['slowk'] = stoch['slowk'] - """ # Stoch fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] - """ - # Stoch RSI - stoch_rsi = ta.STOCHRSI(dataframe) - dataframe['fastd_rsi'] = stoch_rsi['fastd'] - dataframe['fastk_rsi'] = stoch_rsi['fastk'] - """ + # # Stoch RSI + # stoch_rsi = ta.STOCHRSI(dataframe) + # dataframe['fastd_rsi'] = stoch_rsi['fastd'] + # dataframe['fastk_rsi'] = stoch_rsi['fastk'] # Overlap Studies # ------------------------------------ @@ -171,17 +174,16 @@ class {{ strategy }}(IStrategy): dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] - """ - # EMA - Exponential Moving Average - dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) - dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) - dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) - dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) - dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + # # EMA - Exponential Moving Average + # dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) + # dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) + # dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + # dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) + # dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + + # # SMA - Simple Moving Average + # dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) - # SMA - Simple Moving Average - dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) - """ # SAR Parabol dataframe['sar'] = ta.SAR(dataframe) @@ -197,65 +199,57 @@ class {{ strategy }}(IStrategy): # Pattern Recognition - Bullish candlestick patterns # ------------------------------------ - """ - # Hammer: values [0, 100] - dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) - # Inverted Hammer: values [0, 100] - dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) - # Dragonfly Doji: values [0, 100] - dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) - # Piercing Line: values [0, 100] - dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] - # Morningstar: values [0, 100] - dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] - # Three White Soldiers: values [0, 100] - dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] - """ + # # Hammer: values [0, 100] + # dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) + # # Inverted Hammer: values [0, 100] + # dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) + # # Dragonfly Doji: values [0, 100] + # dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) + # # Piercing Line: values [0, 100] + # dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] + # # Morningstar: values [0, 100] + # dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] + # # Three White Soldiers: values [0, 100] + # dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] # Pattern Recognition - Bearish candlestick patterns # ------------------------------------ - """ - # Hanging Man: values [0, 100] - dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) - # Shooting Star: values [0, 100] - dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) - # Gravestone Doji: values [0, 100] - dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) - # Dark Cloud Cover: values [0, 100] - dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) - # Evening Doji Star: values [0, 100] - dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) - # Evening Star: values [0, 100] - dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) - """ + # # Hanging Man: values [0, 100] + # dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) + # # Shooting Star: values [0, 100] + # dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) + # # Gravestone Doji: values [0, 100] + # dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) + # # Dark Cloud Cover: values [0, 100] + # dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) + # # Evening Doji Star: values [0, 100] + # dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) + # # Evening Star: values [0, 100] + # dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) # Pattern Recognition - Bullish/Bearish candlestick patterns # ------------------------------------ - """ - # Three Line Strike: values [0, -100, 100] - dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) - # Spinning Top: values [0, -100, 100] - dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] - # Engulfing: values [0, -100, 100] - dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] - # Harami: values [0, -100, 100] - dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] - # Three Outside Up/Down: values [0, -100, 100] - dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] - # Three Inside Up/Down: values [0, -100, 100] - dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] - """ + # # Three Line Strike: values [0, -100, 100] + # dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) + # # Spinning Top: values [0, -100, 100] + # dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] + # # Engulfing: values [0, -100, 100] + # dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] + # # Harami: values [0, -100, 100] + # dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] + # # Three Outside Up/Down: values [0, -100, 100] + # dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] + # # Three Inside Up/Down: values [0, -100, 100] + # dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] - # Chart type - # ------------------------------------ - """ - # Heikinashi stategy - heikinashi = qtpylib.heikinashi(dataframe) - dataframe['ha_open'] = heikinashi['open'] - dataframe['ha_close'] = heikinashi['close'] - dataframe['ha_high'] = heikinashi['high'] - dataframe['ha_low'] = heikinashi['low'] - """ + # # Chart type + # # ------------------------------------ + # # Heikinashi stategy + # heikinashi = qtpylib.heikinashi(dataframe) + # dataframe['ha_open'] = heikinashi['open'] + # dataframe['ha_close'] = heikinashi['close'] + # dataframe['ha_high'] = heikinashi['high'] + # dataframe['ha_low'] = heikinashi['low'] # Retrieve best bid and best ask from the orderbook # ------------------------------------ diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index d62e6120c..724e52156 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -111,64 +111,60 @@ class SampleStrategy(IStrategy): # 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) + # # 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) + # # Awesome oscillator + # dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) + + # # Commodity Channel Index: values Oversold:<-100, Overbought:>100 + # dataframe['cci'] = ta.CCI(dataframe) - # Commodity Channel Index: values Oversold:<-100, Overbought:>100 - dataframe['cci'] = ta.CCI(dataframe) - """ # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] - # MFI - dataframe['mfi'] = ta.MFI(dataframe) + # # MFI + # dataframe['mfi'] = ta.MFI(dataframe) - """ - # Minus Directional Indicator / Movement - dataframe['minus_dm'] = ta.MINUS_DM(dataframe) - dataframe['minus_di'] = ta.MINUS_DI(dataframe) + # # Minus Directional Indicator / Movement + # dataframe['minus_dm'] = ta.MINUS_DM(dataframe) + # dataframe['minus_di'] = ta.MINUS_DI(dataframe) - # Plus Directional Indicator / Movement - dataframe['plus_dm'] = ta.PLUS_DM(dataframe) - dataframe['plus_di'] = ta.PLUS_DI(dataframe) - dataframe['minus_di'] = ta.MINUS_DI(dataframe) + # # Plus Directional Indicator / Movement + # dataframe['plus_dm'] = ta.PLUS_DM(dataframe) + # dataframe['plus_di'] = ta.PLUS_DI(dataframe) + # dataframe['minus_di'] = ta.MINUS_DI(dataframe) - # ROC - dataframe['roc'] = ta.ROC(dataframe) + # # ROC + # dataframe['roc'] = ta.ROC(dataframe) - # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) - rsi = 0.1 * (dataframe['rsi'] - 50) - dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) + # # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) + # rsi = 0.1 * (dataframe['rsi'] - 50) + # dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) - # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) - dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + # # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) + # dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + + # # Stoch + # stoch = ta.STOCH(dataframe) + # dataframe['slowd'] = stoch['slowd'] + # dataframe['slowk'] = stoch['slowk'] - # Stoch - stoch = ta.STOCH(dataframe) - dataframe['slowd'] = stoch['slowd'] - dataframe['slowk'] = stoch['slowk'] - """ # Stoch fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] - """ - # Stoch RSI - stoch_rsi = ta.STOCHRSI(dataframe) - dataframe['fastd_rsi'] = stoch_rsi['fastd'] - dataframe['fastk_rsi'] = stoch_rsi['fastk'] - """ + # # Stoch RSI + # stoch_rsi = ta.STOCHRSI(dataframe) + # dataframe['fastd_rsi'] = stoch_rsi['fastd'] + # dataframe['fastk_rsi'] = stoch_rsi['fastk'] # Overlap Studies # ------------------------------------ @@ -179,17 +175,16 @@ class SampleStrategy(IStrategy): dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] - """ - # EMA - Exponential Moving Average - dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) - dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) - dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) - dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) - dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + # # EMA - Exponential Moving Average + # dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) + # dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) + # dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + # dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) + # dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + + # # SMA - Simple Moving Average + # dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) - # SMA - Simple Moving Average - dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) - """ # SAR Parabol dataframe['sar'] = ta.SAR(dataframe) @@ -205,65 +200,57 @@ class SampleStrategy(IStrategy): # Pattern Recognition - Bullish candlestick patterns # ------------------------------------ - """ - # Hammer: values [0, 100] - dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) - # Inverted Hammer: values [0, 100] - dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) - # Dragonfly Doji: values [0, 100] - dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) - # Piercing Line: values [0, 100] - dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] - # Morningstar: values [0, 100] - dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] - # Three White Soldiers: values [0, 100] - dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] - """ + # # Hammer: values [0, 100] + # dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) + # # Inverted Hammer: values [0, 100] + # dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) + # # Dragonfly Doji: values [0, 100] + # dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) + # # Piercing Line: values [0, 100] + # dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] + # # Morningstar: values [0, 100] + # dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] + # # Three White Soldiers: values [0, 100] + # dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] # Pattern Recognition - Bearish candlestick patterns # ------------------------------------ - """ - # Hanging Man: values [0, 100] - dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) - # Shooting Star: values [0, 100] - dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) - # Gravestone Doji: values [0, 100] - dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) - # Dark Cloud Cover: values [0, 100] - dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) - # Evening Doji Star: values [0, 100] - dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) - # Evening Star: values [0, 100] - dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) - """ + # # Hanging Man: values [0, 100] + # dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) + # # Shooting Star: values [0, 100] + # dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) + # # Gravestone Doji: values [0, 100] + # dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) + # # Dark Cloud Cover: values [0, 100] + # dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) + # # Evening Doji Star: values [0, 100] + # dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) + # # Evening Star: values [0, 100] + # dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) # Pattern Recognition - Bullish/Bearish candlestick patterns # ------------------------------------ - """ - # Three Line Strike: values [0, -100, 100] - dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) - # Spinning Top: values [0, -100, 100] - dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] - # Engulfing: values [0, -100, 100] - dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] - # Harami: values [0, -100, 100] - dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] - # Three Outside Up/Down: values [0, -100, 100] - dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] - # Three Inside Up/Down: values [0, -100, 100] - dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] - """ + # # Three Line Strike: values [0, -100, 100] + # dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) + # # Spinning Top: values [0, -100, 100] + # dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] + # # Engulfing: values [0, -100, 100] + # dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] + # # Harami: values [0, -100, 100] + # dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] + # # Three Outside Up/Down: values [0, -100, 100] + # dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] + # # Three Inside Up/Down: values [0, -100, 100] + # dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] - # Chart type - # ------------------------------------ - """ - # Heikinashi stategy - heikinashi = qtpylib.heikinashi(dataframe) - dataframe['ha_open'] = heikinashi['open'] - dataframe['ha_close'] = heikinashi['close'] - dataframe['ha_high'] = heikinashi['high'] - dataframe['ha_low'] = heikinashi['low'] - """ + # # Chart type + # # ------------------------------------ + # # Heikinashi stategy + # heikinashi = qtpylib.heikinashi(dataframe) + # dataframe['ha_open'] = heikinashi['open'] + # dataframe['ha_close'] = heikinashi['close'] + # dataframe['ha_high'] = heikinashi['high'] + # dataframe['ha_low'] = heikinashi['low'] # Retrieve best bid and best ask from the orderbook # ------------------------------------ From 5e5ef21f61f33a50c54b151cdf9081983394bc1b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Nov 2019 06:49:16 +0100 Subject: [PATCH 25/76] Align example imports --- freqtrade/templates/base_hyperopt.py.j2 | 7 ++++--- freqtrade/templates/base_strategy.py.j2 | 10 ++++++---- freqtrade/templates/sample_hyperopt.py | 9 +++++++-- freqtrade/templates/sample_hyperopt_advanced.py | 13 +++++++++---- freqtrade/templates/sample_strategy.py | 10 ++++++---- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index 75aedbf86..c0f4e3292 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -1,18 +1,19 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement +# --- Do not remove these libs --- from functools import reduce from typing import Any, Callable, Dict, List -from pandas import DataFrame -import pandas as pd # noqa import numpy as np # noqa +import pandas as pd # noqa +from pandas import DataFrame from skopt.space import Categorical, Dimension, Integer, Real # noqa from freqtrade.optimize.hyperopt_interface import IHyperOpt # -------------------------------- # Add your lib to import here -import talib.abstract as ta +import talib.abstract as ta # noqa import freqtrade.vendor.qtpylib.indicators as qtpylib diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 174c801ee..ccb8949e9 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -1,11 +1,13 @@ +# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # --- Do not remove these libs --- -from freqtrade.strategy.interface import IStrategy -from pandas import DataFrame -import pandas as pd # noqa import numpy as np # noqa -# -------------------------------- +import pandas as pd # noqa +from pandas import DataFrame +from freqtrade.strategy.interface import IStrategy + +# -------------------------------- # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib diff --git a/freqtrade/templates/sample_hyperopt.py b/freqtrade/templates/sample_hyperopt.py index 77afb2b98..f1dcb404a 100644 --- a/freqtrade/templates/sample_hyperopt.py +++ b/freqtrade/templates/sample_hyperopt.py @@ -1,16 +1,21 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement +# --- Do not remove these libs --- from functools import reduce from typing import Any, Callable, Dict, List import numpy as np # noqa -import talib.abstract as ta # noqa +import pandas as pd # noqa from pandas import DataFrame from skopt.space import Categorical, Dimension, Integer, Real # noqa -import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.optimize.hyperopt_interface import IHyperOpt +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta # noqa +import freqtrade.vendor.qtpylib.indicators as qtpylib + class SampleHyperOpt(IHyperOpt): """ diff --git a/freqtrade/templates/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py index 7ababc16c..5634c21ea 100644 --- a/freqtrade/templates/sample_hyperopt_advanced.py +++ b/freqtrade/templates/sample_hyperopt_advanced.py @@ -1,16 +1,21 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement +# --- Do not remove these libs --- from functools import reduce from typing import Any, Callable, Dict, List -import numpy as np # noqa F401 -import talib.abstract as ta +import numpy as np # noqa +import pandas as pd # noqa from pandas import DataFrame -from skopt.space import Categorical, Dimension, Integer, Real +from skopt.space import Categorical, Dimension, Integer, Real # noqa -import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.optimize.hyperopt_interface import IHyperOpt +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta # noqa +import freqtrade.vendor.qtpylib.indicators as qtpylib + class AdvancedSampleHyperOpt(IHyperOpt): """ diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 724e52156..38a45c1f2 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -1,14 +1,16 @@ +# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement # --- Do not remove these libs --- -from freqtrade.strategy.interface import IStrategy +import numpy as np # noqa +import pandas as pd # noqa from pandas import DataFrame -# -------------------------------- +from freqtrade.strategy.interface import IStrategy + +# -------------------------------- # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib -import pandas as pd # noqa -import numpy as np # noqa # This class is a sample. Feel free to customize it. From b3dbb818388da57ee1bc6611e0b25dfdbec36367 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Nov 2019 07:13:56 +0100 Subject: [PATCH 26/76] Add subtemplates --- freqtrade/templates/base_strategy.py.j2 | 170 +----------------- .../templates/subtemplates/buy_trend_full.j2 | 3 + .../templates/subtemplates/indicators_full.j2 | 161 +++++++++++++++++ .../templates/subtemplates/sell_trend_full.j2 | 3 + freqtrade/utils.py | 3 +- 5 files changed, 172 insertions(+), 168 deletions(-) create mode 100644 freqtrade/templates/subtemplates/buy_trend_full.j2 create mode 100644 freqtrade/templates/subtemplates/indicators_full.j2 create mode 100644 freqtrade/templates/subtemplates/sell_trend_full.j2 diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index ccb8949e9..4c5fe9a0b 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -102,167 +102,7 @@ class {{ strategy }}(IStrategy): :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ - - # Momentum Indicators - # ------------------------------------ - - # 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) - - # # Commodity Channel Index: values Oversold:<-100, Overbought:>100 - # dataframe['cci'] = ta.CCI(dataframe) - - # MACD - macd = ta.MACD(dataframe) - dataframe['macd'] = macd['macd'] - dataframe['macdsignal'] = macd['macdsignal'] - dataframe['macdhist'] = macd['macdhist'] - - # # MFI - # dataframe['mfi'] = ta.MFI(dataframe) - - # # Minus Directional Indicator / Movement - # dataframe['minus_dm'] = ta.MINUS_DM(dataframe) - # dataframe['minus_di'] = ta.MINUS_DI(dataframe) - - # # Plus Directional Indicator / Movement - # dataframe['plus_dm'] = ta.PLUS_DM(dataframe) - # dataframe['plus_di'] = ta.PLUS_DI(dataframe) - # dataframe['minus_di'] = ta.MINUS_DI(dataframe) - - # # ROC - # dataframe['roc'] = ta.ROC(dataframe) - - # # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) - # rsi = 0.1 * (dataframe['rsi'] - 50) - # dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) - - # # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) - # dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) - - # # Stoch - # stoch = ta.STOCH(dataframe) - # dataframe['slowd'] = stoch['slowd'] - # dataframe['slowk'] = stoch['slowk'] - - # Stoch fast - stoch_fast = ta.STOCHF(dataframe) - dataframe['fastd'] = stoch_fast['fastd'] - dataframe['fastk'] = stoch_fast['fastk'] - - # # Stoch RSI - # stoch_rsi = ta.STOCHRSI(dataframe) - # dataframe['fastd_rsi'] = stoch_rsi['fastd'] - # dataframe['fastk_rsi'] = stoch_rsi['fastk'] - - # Overlap Studies - # ------------------------------------ - - # Bollinger bands - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) - dataframe['bb_lowerband'] = bollinger['lower'] - dataframe['bb_middleband'] = bollinger['mid'] - dataframe['bb_upperband'] = bollinger['upper'] - - # # EMA - Exponential Moving Average - # dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) - # dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) - # dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) - # dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) - # dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) - - # # SMA - Simple Moving Average - # dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) - - # SAR Parabol - dataframe['sar'] = ta.SAR(dataframe) - - # TEMA - Triple Exponential Moving Average - dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) - - # Cycle Indicator - # ------------------------------------ - # Hilbert Transform Indicator - SineWave - hilbert = ta.HT_SINE(dataframe) - dataframe['htsine'] = hilbert['sine'] - dataframe['htleadsine'] = hilbert['leadsine'] - - # Pattern Recognition - Bullish candlestick patterns - # ------------------------------------ - # # Hammer: values [0, 100] - # dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) - # # Inverted Hammer: values [0, 100] - # dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) - # # Dragonfly Doji: values [0, 100] - # dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) - # # Piercing Line: values [0, 100] - # dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] - # # Morningstar: values [0, 100] - # dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] - # # Three White Soldiers: values [0, 100] - # dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] - - # Pattern Recognition - Bearish candlestick patterns - # ------------------------------------ - # # Hanging Man: values [0, 100] - # dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) - # # Shooting Star: values [0, 100] - # dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) - # # Gravestone Doji: values [0, 100] - # dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) - # # Dark Cloud Cover: values [0, 100] - # dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) - # # Evening Doji Star: values [0, 100] - # dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) - # # Evening Star: values [0, 100] - # dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) - - # Pattern Recognition - Bullish/Bearish candlestick patterns - # ------------------------------------ - # # Three Line Strike: values [0, -100, 100] - # dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) - # # Spinning Top: values [0, -100, 100] - # dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] - # # Engulfing: values [0, -100, 100] - # dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] - # # Harami: values [0, -100, 100] - # dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] - # # Three Outside Up/Down: values [0, -100, 100] - # dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] - # # Three Inside Up/Down: values [0, -100, 100] - # dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] - - # # Chart type - # # ------------------------------------ - # # Heikinashi stategy - # heikinashi = qtpylib.heikinashi(dataframe) - # dataframe['ha_open'] = heikinashi['open'] - # dataframe['ha_close'] = heikinashi['close'] - # dataframe['ha_high'] = heikinashi['high'] - # dataframe['ha_low'] = heikinashi['low'] - - # Retrieve best bid and best ask from the orderbook - # ------------------------------------ - """ - # first check if dataprovider is available - if self.dp: - if self.dp.runmode in ('live', 'dry_run'): - ob = self.dp.orderbook(metadata['pair'], 1) - dataframe['best_bid'] = ob['bids'][0][0] - dataframe['best_ask'] = ob['asks'][0][0] - """ + {% filter indent(8) %}{% include 'subtemplates/indicators_' + subtemplates + '.j2' %}{% endfilter %} return dataframe @@ -275,9 +115,7 @@ class {{ strategy }}(IStrategy): """ dataframe.loc[ ( - (qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 - (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle - (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising + {% filter indent(16) %}{% include 'subtemplates/buy_trend_' + subtemplates + '.j2' %}{% endfilter %} (dataframe['volume'] > 0) # Make sure Volume is not 0 ), 'buy'] = 1 @@ -293,9 +131,7 @@ class {{ strategy }}(IStrategy): """ dataframe.loc[ ( - (qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 - (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle - (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling + {% filter indent(16) %}{% include 'subtemplates/sell_trend_' + subtemplates + '.j2' %}{% endfilter %} (dataframe['volume'] > 0) # Make sure Volume is not 0 ), 'sell'] = 1 diff --git a/freqtrade/templates/subtemplates/buy_trend_full.j2 b/freqtrade/templates/subtemplates/buy_trend_full.j2 new file mode 100644 index 000000000..1a0d326b3 --- /dev/null +++ b/freqtrade/templates/subtemplates/buy_trend_full.j2 @@ -0,0 +1,3 @@ +(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 +(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle +(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising diff --git a/freqtrade/templates/subtemplates/indicators_full.j2 b/freqtrade/templates/subtemplates/indicators_full.j2 new file mode 100644 index 000000000..395808776 --- /dev/null +++ b/freqtrade/templates/subtemplates/indicators_full.j2 @@ -0,0 +1,161 @@ + +# Momentum Indicators +# ------------------------------------ + +# 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) + +# # Commodity Channel Index: values Oversold:<-100, Overbought:>100 +# dataframe['cci'] = ta.CCI(dataframe) + +# MACD +macd = ta.MACD(dataframe) +dataframe['macd'] = macd['macd'] +dataframe['macdsignal'] = macd['macdsignal'] +dataframe['macdhist'] = macd['macdhist'] + +# # MFI +# dataframe['mfi'] = ta.MFI(dataframe) + +# # Minus Directional Indicator / Movement +# dataframe['minus_dm'] = ta.MINUS_DM(dataframe) +# dataframe['minus_di'] = ta.MINUS_DI(dataframe) + +# # Plus Directional Indicator / Movement +# dataframe['plus_dm'] = ta.PLUS_DM(dataframe) +# dataframe['plus_di'] = ta.PLUS_DI(dataframe) +# dataframe['minus_di'] = ta.MINUS_DI(dataframe) + +# # ROC +# dataframe['roc'] = ta.ROC(dataframe) + +# # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) +# rsi = 0.1 * (dataframe['rsi'] - 50) +# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1) + +# # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) +# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + +# # Stoch +# stoch = ta.STOCH(dataframe) +# dataframe['slowd'] = stoch['slowd'] +# dataframe['slowk'] = stoch['slowk'] + +# Stoch fast +stoch_fast = ta.STOCHF(dataframe) +dataframe['fastd'] = stoch_fast['fastd'] +dataframe['fastk'] = stoch_fast['fastk'] + +# # Stoch RSI +# stoch_rsi = ta.STOCHRSI(dataframe) +# dataframe['fastd_rsi'] = stoch_rsi['fastd'] +# dataframe['fastk_rsi'] = stoch_rsi['fastk'] + +# Overlap Studies +# ------------------------------------ + +# Bollinger bands +bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) +dataframe['bb_lowerband'] = bollinger['lower'] +dataframe['bb_middleband'] = bollinger['mid'] +dataframe['bb_upperband'] = bollinger['upper'] + +# # EMA - Exponential Moving Average +# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) +# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) +# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) +# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) +# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + +# # SMA - Simple Moving Average +# dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) + +# SAR Parabol +dataframe['sar'] = ta.SAR(dataframe) + +# TEMA - Triple Exponential Moving Average +dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + +# Cycle Indicator +# ------------------------------------ +# Hilbert Transform Indicator - SineWave +hilbert = ta.HT_SINE(dataframe) +dataframe['htsine'] = hilbert['sine'] +dataframe['htleadsine'] = hilbert['leadsine'] + +# Pattern Recognition - Bullish candlestick patterns +# ------------------------------------ +# # Hammer: values [0, 100] +# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) +# # Inverted Hammer: values [0, 100] +# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) +# # Dragonfly Doji: values [0, 100] +# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) +# # Piercing Line: values [0, 100] +# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] +# # Morningstar: values [0, 100] +# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] +# # Three White Soldiers: values [0, 100] +# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] + +# Pattern Recognition - Bearish candlestick patterns +# ------------------------------------ +# # Hanging Man: values [0, 100] +# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) +# # Shooting Star: values [0, 100] +# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) +# # Gravestone Doji: values [0, 100] +# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) +# # Dark Cloud Cover: values [0, 100] +# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) +# # Evening Doji Star: values [0, 100] +# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) +# # Evening Star: values [0, 100] +# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) + +# Pattern Recognition - Bullish/Bearish candlestick patterns +# ------------------------------------ +# # Three Line Strike: values [0, -100, 100] +# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) +# # Spinning Top: values [0, -100, 100] +# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] +# # Engulfing: values [0, -100, 100] +# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] +# # Harami: values [0, -100, 100] +# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] +# # Three Outside Up/Down: values [0, -100, 100] +# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] +# # Three Inside Up/Down: values [0, -100, 100] +# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] + +# # Chart type +# # ------------------------------------ +# # Heikinashi stategy +# heikinashi = qtpylib.heikinashi(dataframe) +# dataframe['ha_open'] = heikinashi['open'] +# dataframe['ha_close'] = heikinashi['close'] +# dataframe['ha_high'] = heikinashi['high'] +# dataframe['ha_low'] = heikinashi['low'] + +# Retrieve best bid and best ask from the orderbook +# ------------------------------------ +""" +# first check if dataprovider is available +if self.dp: +if self.dp.runmode in ('live', 'dry_run'): + ob = self.dp.orderbook(metadata['pair'], 1) + dataframe['best_bid'] = ob['bids'][0][0] + dataframe['best_ask'] = ob['asks'][0][0] +""" diff --git a/freqtrade/templates/subtemplates/sell_trend_full.j2 b/freqtrade/templates/subtemplates/sell_trend_full.j2 new file mode 100644 index 000000000..36c08c947 --- /dev/null +++ b/freqtrade/templates/subtemplates/sell_trend_full.j2 @@ -0,0 +1,3 @@ +(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 +(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle +(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 3b37c6895..47bb5e3f6 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -106,7 +106,8 @@ def start_new_strategy(args: Dict[str, Any]) -> None: "Please choose another Strategy Name.") strategy_text = render_template(templatefile='base_strategy.py.j2', - arguments={"strategy": args["strategy"]}) + arguments={"strategy": args["strategy"], + "subtemplates": 'full'}) logger.info(f"Writing strategy to `{new_path}`.") new_path.write_text(strategy_text) From f26c40082dc806cd876debd9ccbfec0179f41ae2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Nov 2019 07:21:19 +0100 Subject: [PATCH 27/76] Allow selection of templates for strategy --- freqtrade/configuration/arguments.py | 4 ++-- freqtrade/configuration/cli_options.py | 8 ++++++++ .../templates/subtemplates/buy_trend_minimal.j2 | 1 + .../templates/subtemplates/indicators_full.j2 | 8 ++++---- .../subtemplates/indicators_minimal.j2 | 17 +++++++++++++++++ .../subtemplates/sell_trend_minimal.j2 | 1 + freqtrade/utils.py | 6 ++++-- 7 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 freqtrade/templates/subtemplates/buy_trend_minimal.j2 create mode 100644 freqtrade/templates/subtemplates/indicators_minimal.j2 create mode 100644 freqtrade/templates/subtemplates/sell_trend_minimal.j2 diff --git a/freqtrade/configuration/arguments.py b/freqtrade/configuration/arguments.py index 5cc56a8bc..b23366d7a 100644 --- a/freqtrade/configuration/arguments.py +++ b/freqtrade/configuration/arguments.py @@ -39,9 +39,9 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] -ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy"] +ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] -ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt"] +ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"] ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange", "timeframes", "erase"] diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index d7a496aa7..be9397975 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -339,6 +339,14 @@ AVAILABLE_CLI_OPTIONS = { help='Clean all existing data for the selected exchange/pairs/timeframes.', action='store_true', ), + # Templating options + "template": Arg( + '--template', + help='Use a template which is either `minimal` or ' + '`full` (containing multiple sample indicators).', + choices=['full', 'minimal'], + default='full', + ), # Plot dataframe "indicators1": Arg( '--indicators1', diff --git a/freqtrade/templates/subtemplates/buy_trend_minimal.j2 b/freqtrade/templates/subtemplates/buy_trend_minimal.j2 new file mode 100644 index 000000000..6a4079cf3 --- /dev/null +++ b/freqtrade/templates/subtemplates/buy_trend_minimal.j2 @@ -0,0 +1 @@ +(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 diff --git a/freqtrade/templates/subtemplates/indicators_full.j2 b/freqtrade/templates/subtemplates/indicators_full.j2 index 395808776..33dd85311 100644 --- a/freqtrade/templates/subtemplates/indicators_full.j2 +++ b/freqtrade/templates/subtemplates/indicators_full.j2 @@ -154,8 +154,8 @@ dataframe['htleadsine'] = hilbert['leadsine'] """ # first check if dataprovider is available if self.dp: -if self.dp.runmode in ('live', 'dry_run'): - ob = self.dp.orderbook(metadata['pair'], 1) - dataframe['best_bid'] = ob['bids'][0][0] - dataframe['best_ask'] = ob['asks'][0][0] + if self.dp.runmode in ('live', 'dry_run'): + ob = self.dp.orderbook(metadata['pair'], 1) + dataframe['best_bid'] = ob['bids'][0][0] + dataframe['best_ask'] = ob['asks'][0][0] """ diff --git a/freqtrade/templates/subtemplates/indicators_minimal.j2 b/freqtrade/templates/subtemplates/indicators_minimal.j2 new file mode 100644 index 000000000..7d75b4610 --- /dev/null +++ b/freqtrade/templates/subtemplates/indicators_minimal.j2 @@ -0,0 +1,17 @@ + +# Momentum Indicators +# ------------------------------------ + +# RSI +dataframe['rsi'] = ta.RSI(dataframe) + +# Retrieve best bid and best ask from the orderbook +# ------------------------------------ +""" +# first check if dataprovider is available +if self.dp: + if self.dp.runmode in ('live', 'dry_run'): + ob = self.dp.orderbook(metadata['pair'], 1) + dataframe['best_bid'] = ob['bids'][0][0] + dataframe['best_ask'] = ob['asks'][0][0] +""" diff --git a/freqtrade/templates/subtemplates/sell_trend_minimal.j2 b/freqtrade/templates/subtemplates/sell_trend_minimal.j2 new file mode 100644 index 000000000..42a7b81a2 --- /dev/null +++ b/freqtrade/templates/subtemplates/sell_trend_minimal.j2 @@ -0,0 +1 @@ +(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 47bb5e3f6..4657e58fc 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -107,7 +107,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None: strategy_text = render_template(templatefile='base_strategy.py.j2', arguments={"strategy": args["strategy"], - "subtemplates": 'full'}) + "subtemplates": args['template']}) logger.info(f"Writing strategy to `{new_path}`.") new_path.write_text(strategy_text) @@ -130,7 +130,9 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: "Please choose another Strategy Name.") strategy_text = render_template(templatefile='base_hyperopt.py.j2', - arguments={"hyperopt": args["hyperopt"]}) + arguments={"hyperopt": args["hyperopt"], + "subtemplates": args['template'] + }) logger.info(f"Writing hyperopt to `{new_path}`.") new_path.write_text(strategy_text) From f23f659ac590618ec8a4b232d49db079b61796d1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Nov 2019 19:28:53 +0100 Subject: [PATCH 28/76] Use strings instead of subtemplates --- freqtrade/misc.py | 2 +- freqtrade/templates/base_strategy.py.j2 | 6 +++--- freqtrade/utils.py | 9 ++++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 6497a4727..bcba78cf0 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -129,7 +129,7 @@ def plural(num, singular: str, plural: str = None) -> str: return singular if (num == 1 or num == -1) else plural or singular + 's' -def render_template(templatefile: str, arguments: dict): +def render_template(templatefile: str, arguments: dict = {}): from jinja2 import Environment, PackageLoader, select_autoescape diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 4c5fe9a0b..73a4c7a5a 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -102,7 +102,7 @@ class {{ strategy }}(IStrategy): :param metadata: Additional information, like the currently traded pair :return: a Dataframe with all mandatory indicators for the strategies """ - {% filter indent(8) %}{% include 'subtemplates/indicators_' + subtemplates + '.j2' %}{% endfilter %} + {{ indicators | indent(8) }} return dataframe @@ -115,7 +115,7 @@ class {{ strategy }}(IStrategy): """ dataframe.loc[ ( - {% filter indent(16) %}{% include 'subtemplates/buy_trend_' + subtemplates + '.j2' %}{% endfilter %} + {{ buy_trend | indent(16) }} (dataframe['volume'] > 0) # Make sure Volume is not 0 ), 'buy'] = 1 @@ -131,7 +131,7 @@ class {{ strategy }}(IStrategy): """ dataframe.loc[ ( - {% filter indent(16) %}{% include 'subtemplates/sell_trend_' + subtemplates + '.j2' %}{% endfilter %} + {{ sell_trend | indent(16) }} (dataframe['volume'] > 0) # Make sure Volume is not 0 ), 'sell'] = 1 diff --git a/freqtrade/utils.py b/freqtrade/utils.py index 4657e58fc..e94de4f3e 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -105,9 +105,16 @@ def start_new_strategy(args: Dict[str, Any]) -> None: raise OperationalException(f"`{new_path}` already exists. " "Please choose another Strategy Name.") + indicators = render_template(templatefile=f"subtemplates/indicators_{args['template']}.j2",) + buy_trend = render_template(templatefile=f"subtemplates/buy_trend_{args['template']}.j2",) + sell_trend = render_template(templatefile=f"subtemplates/sell_trend_{args['template']}.j2",) + strategy_text = render_template(templatefile='base_strategy.py.j2', arguments={"strategy": args["strategy"], - "subtemplates": args['template']}) + "indicators": indicators, + "buy_trend": buy_trend, + "sell_trend": sell_trend, + }) logger.info(f"Writing strategy to `{new_path}`.") new_path.write_text(strategy_text) From 5f8fcebb8860831c4794e05318cecb36189cf5b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Nov 2019 19:41:57 +0100 Subject: [PATCH 29/76] Parametrize hyperopt file --- freqtrade/templates/base_hyperopt.py.j2 | 40 ++----------------- .../subtemplates/hyperopt_buy_guards_full.j2 | 8 ++++ .../hyperopt_buy_guards_minimal.j2 | 2 + .../subtemplates/hyperopt_buy_space_full.j2 | 9 +++++ .../hyperopt_buy_space_minimal.j2 | 3 ++ .../subtemplates/hyperopt_sell_guards_full.j2 | 8 ++++ .../hyperopt_sell_guards_minimal.j2 | 2 + .../subtemplates/hyperopt_sell_space_full.j2 | 11 +++++ .../hyperopt_sell_space_minimal.j2 | 5 +++ freqtrade/utils.py | 14 ++++++- 10 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 freqtrade/templates/subtemplates/hyperopt_buy_guards_full.j2 create mode 100644 freqtrade/templates/subtemplates/hyperopt_buy_guards_minimal.j2 create mode 100644 freqtrade/templates/subtemplates/hyperopt_buy_space_full.j2 create mode 100644 freqtrade/templates/subtemplates/hyperopt_buy_space_minimal.j2 create mode 100644 freqtrade/templates/subtemplates/hyperopt_sell_guards_full.j2 create mode 100644 freqtrade/templates/subtemplates/hyperopt_sell_guards_minimal.j2 create mode 100644 freqtrade/templates/subtemplates/hyperopt_sell_space_full.j2 create mode 100644 freqtrade/templates/subtemplates/hyperopt_sell_space_minimal.j2 diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index c0f4e3292..05ba08b81 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -48,14 +48,7 @@ class {{ hyperopt }}(IHyperOpt): conditions = [] # GUARDS AND TRENDS - if 'mfi-enabled' in params and params['mfi-enabled']: - conditions.append(dataframe['mfi'] < params['mfi-value']) - if 'fastd-enabled' in params and params['fastd-enabled']: - conditions.append(dataframe['fastd'] < params['fastd-value']) - if 'adx-enabled' in params and params['adx-enabled']: - conditions.append(dataframe['adx'] > params['adx-value']) - if 'rsi-enabled' in params and params['rsi-enabled']: - conditions.append(dataframe['rsi'] < params['rsi-value']) + {{ buy_guards | indent(12) }} # TRIGGERS if 'trigger' in params: @@ -85,15 +78,7 @@ class {{ hyperopt }}(IHyperOpt): Define your Hyperopt space for searching buy strategy parameters. """ return [ - Integer(10, 25, name='mfi-value'), - Integer(15, 45, name='fastd-value'), - Integer(20, 50, name='adx-value'), - Integer(20, 40, name='rsi-value'), - Categorical([True, False], name='mfi-enabled'), - Categorical([True, False], name='fastd-enabled'), - Categorical([True, False], name='adx-enabled'), - Categorical([True, False], name='rsi-enabled'), - Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') + {{ buy_space | indent(12) }} ] @staticmethod @@ -108,14 +93,7 @@ class {{ hyperopt }}(IHyperOpt): conditions = [] # GUARDS AND TRENDS - if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: - conditions.append(dataframe['mfi'] > params['sell-mfi-value']) - if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']: - conditions.append(dataframe['fastd'] > params['sell-fastd-value']) - if 'sell-adx-enabled' in params and params['sell-adx-enabled']: - conditions.append(dataframe['adx'] < params['sell-adx-value']) - if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']: - conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + {{ sell_guards | indent(12) }} # TRIGGERS if 'sell-trigger' in params: @@ -145,15 +123,5 @@ class {{ hyperopt }}(IHyperOpt): Define your Hyperopt space for searching sell strategy parameters. """ return [ - Integer(75, 100, name='sell-mfi-value'), - Integer(50, 100, name='sell-fastd-value'), - Integer(50, 100, name='sell-adx-value'), - Integer(60, 100, name='sell-rsi-value'), - Categorical([True, False], name='sell-mfi-enabled'), - Categorical([True, False], name='sell-fastd-enabled'), - Categorical([True, False], name='sell-adx-enabled'), - Categorical([True, False], name='sell-rsi-enabled'), - Categorical(['sell-bb_upper', - 'sell-macd_cross_signal', - 'sell-sar_reversal'], name='sell-trigger') + {{ sell_space | indent(12) }} ] diff --git a/freqtrade/templates/subtemplates/hyperopt_buy_guards_full.j2 b/freqtrade/templates/subtemplates/hyperopt_buy_guards_full.j2 new file mode 100644 index 000000000..5b967f4ed --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_buy_guards_full.j2 @@ -0,0 +1,8 @@ +if params.get('mfi-enabled'): + conditions.append(dataframe['mfi'] < params['mfi-value']) +if params.get('fastd-enabled'): + conditions.append(dataframe['fastd'] < params['fastd-value']) +if params.get('adx-enabled'): + conditions.append(dataframe['adx'] > params['adx-value']) +if params.get('rsi-enabled'): + conditions.append(dataframe['rsi'] < params['rsi-value']) diff --git a/freqtrade/templates/subtemplates/hyperopt_buy_guards_minimal.j2 b/freqtrade/templates/subtemplates/hyperopt_buy_guards_minimal.j2 new file mode 100644 index 000000000..5e1022f59 --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_buy_guards_minimal.j2 @@ -0,0 +1,2 @@ +if params.get('rsi-enabled'): + conditions.append(dataframe['rsi'] < params['rsi-value']) diff --git a/freqtrade/templates/subtemplates/hyperopt_buy_space_full.j2 b/freqtrade/templates/subtemplates/hyperopt_buy_space_full.j2 new file mode 100644 index 000000000..29bafbd93 --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_buy_space_full.j2 @@ -0,0 +1,9 @@ +Integer(10, 25, name='mfi-value'), +Integer(15, 45, name='fastd-value'), +Integer(20, 50, name='adx-value'), +Integer(20, 40, name='rsi-value'), +Categorical([True, False], name='mfi-enabled'), +Categorical([True, False], name='fastd-enabled'), +Categorical([True, False], name='adx-enabled'), +Categorical([True, False], name='rsi-enabled'), +Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') diff --git a/freqtrade/templates/subtemplates/hyperopt_buy_space_minimal.j2 b/freqtrade/templates/subtemplates/hyperopt_buy_space_minimal.j2 new file mode 100644 index 000000000..5ddf537fb --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_buy_space_minimal.j2 @@ -0,0 +1,3 @@ +Integer(20, 40, name='rsi-value'), +Categorical([True, False], name='rsi-enabled'), +Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') diff --git a/freqtrade/templates/subtemplates/hyperopt_sell_guards_full.j2 b/freqtrade/templates/subtemplates/hyperopt_sell_guards_full.j2 new file mode 100644 index 000000000..bd7b499f4 --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_sell_guards_full.j2 @@ -0,0 +1,8 @@ +if params.get('sell-mfi-enabled'): + conditions.append(dataframe['mfi'] > params['sell-mfi-value']) +if params.get('sell-fastd-enabled'): + conditions.append(dataframe['fastd'] > params['sell-fastd-value']) +if params.get('sell-adx-enabled'): + conditions.append(dataframe['adx'] < params['sell-adx-value']) +if params.get('sell-rsi-enabled'): + conditions.append(dataframe['rsi'] > params['sell-rsi-value']) diff --git a/freqtrade/templates/subtemplates/hyperopt_sell_guards_minimal.j2 b/freqtrade/templates/subtemplates/hyperopt_sell_guards_minimal.j2 new file mode 100644 index 000000000..8b4adebf6 --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_sell_guards_minimal.j2 @@ -0,0 +1,2 @@ +if params.get('sell-rsi-enabled'): + conditions.append(dataframe['rsi'] > params['sell-rsi-value']) diff --git a/freqtrade/templates/subtemplates/hyperopt_sell_space_full.j2 b/freqtrade/templates/subtemplates/hyperopt_sell_space_full.j2 new file mode 100644 index 000000000..46469d532 --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_sell_space_full.j2 @@ -0,0 +1,11 @@ +Integer(75, 100, name='sell-mfi-value'), +Integer(50, 100, name='sell-fastd-value'), +Integer(50, 100, name='sell-adx-value'), +Integer(60, 100, name='sell-rsi-value'), +Categorical([True, False], name='sell-mfi-enabled'), +Categorical([True, False], name='sell-fastd-enabled'), +Categorical([True, False], name='sell-adx-enabled'), +Categorical([True, False], name='sell-rsi-enabled'), +Categorical(['sell-bb_upper', + 'sell-macd_cross_signal', + 'sell-sar_reversal'], name='sell-trigger') diff --git a/freqtrade/templates/subtemplates/hyperopt_sell_space_minimal.j2 b/freqtrade/templates/subtemplates/hyperopt_sell_space_minimal.j2 new file mode 100644 index 000000000..dfb110543 --- /dev/null +++ b/freqtrade/templates/subtemplates/hyperopt_sell_space_minimal.j2 @@ -0,0 +1,5 @@ +Integer(60, 100, name='sell-rsi-value'), +Categorical([True, False], name='sell-rsi-enabled'), +Categorical(['sell-bb_upper', + 'sell-macd_cross_signal', + 'sell-sar_reversal'], name='sell-trigger') diff --git a/freqtrade/utils.py b/freqtrade/utils.py index e94de4f3e..e7b6eff4a 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -136,9 +136,21 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: raise OperationalException(f"`{new_path}` already exists. " "Please choose another Strategy Name.") + buy_guards = render_template( + templatefile=f"subtemplates/hyperopt_buy_guards_{args['template']}.j2",) + sell_guards = render_template( + templatefile=f"subtemplates/hyperopt_sell_guards_{args['template']}.j2",) + buy_space = render_template( + templatefile=f"subtemplates/hyperopt_buy_space_{args['template']}.j2",) + sell_space = render_template( + templatefile=f"subtemplates/hyperopt_sell_space_{args['template']}.j2",) + strategy_text = render_template(templatefile='base_hyperopt.py.j2', arguments={"hyperopt": args["hyperopt"], - "subtemplates": args['template'] + "buy_guards": buy_guards, + "sell_guards": sell_guards, + "buy_space": buy_space, + "sell_space": sell_space, }) logger.info(f"Writing hyperopt to `{new_path}`.") From 210d468a9b1cff8c0d5373de185f60102d8e92e9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Nov 2019 20:01:08 +0100 Subject: [PATCH 30/76] Reinstate mfi ... --- freqtrade/templates/sample_strategy.py | 4 +- .../templates/subtemplates/indicators_full.j2 | 4 +- freqtrade/utils.py | 78 +++++++++++-------- 3 files changed, 50 insertions(+), 36 deletions(-) diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 38a45c1f2..02bf24e7e 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -131,8 +131,8 @@ class SampleStrategy(IStrategy): dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] - # # MFI - # dataframe['mfi'] = ta.MFI(dataframe) + # MFI + dataframe['mfi'] = ta.MFI(dataframe) # # Minus Directional Indicator / Movement # dataframe['minus_dm'] = ta.MINUS_DM(dataframe) diff --git a/freqtrade/templates/subtemplates/indicators_full.j2 b/freqtrade/templates/subtemplates/indicators_full.j2 index 33dd85311..879a2daa0 100644 --- a/freqtrade/templates/subtemplates/indicators_full.j2 +++ b/freqtrade/templates/subtemplates/indicators_full.j2 @@ -26,8 +26,8 @@ dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] -# # MFI -# dataframe['mfi'] = ta.MFI(dataframe) +# MFI +dataframe['mfi'] = ta.MFI(dataframe) # # Minus Directional Indicator / Movement # dataframe['minus_dm'] = ta.MINUS_DM(dataframe) diff --git a/freqtrade/utils.py b/freqtrade/utils.py index e7b6eff4a..c71080d5a 100644 --- a/freqtrade/utils.py +++ b/freqtrade/utils.py @@ -91,6 +91,25 @@ def start_create_userdir(args: Dict[str, Any]) -> None: sys.exit(1) +def deploy_new_strategy(strategy_name, strategy_path: Path, subtemplate: str): + """ + Deploy new strategy from template to strategy_path + """ + indicators = render_template(templatefile=f"subtemplates/indicators_{subtemplate}.j2",) + buy_trend = render_template(templatefile=f"subtemplates/buy_trend_{subtemplate}.j2",) + sell_trend = render_template(templatefile=f"subtemplates/sell_trend_{subtemplate}.j2",) + + strategy_text = render_template(templatefile='base_strategy.py.j2', + arguments={"strategy": strategy_name, + "indicators": indicators, + "buy_trend": buy_trend, + "sell_trend": sell_trend, + }) + + logger.info(f"Writing strategy to `{strategy_path}`.") + strategy_path.write_text(strategy_text) + + def start_new_strategy(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) @@ -105,23 +124,37 @@ def start_new_strategy(args: Dict[str, Any]) -> None: raise OperationalException(f"`{new_path}` already exists. " "Please choose another Strategy Name.") - indicators = render_template(templatefile=f"subtemplates/indicators_{args['template']}.j2",) - buy_trend = render_template(templatefile=f"subtemplates/buy_trend_{args['template']}.j2",) - sell_trend = render_template(templatefile=f"subtemplates/sell_trend_{args['template']}.j2",) + deploy_new_strategy(args['strategy'], new_path, args['template']) - strategy_text = render_template(templatefile='base_strategy.py.j2', - arguments={"strategy": args["strategy"], - "indicators": indicators, - "buy_trend": buy_trend, - "sell_trend": sell_trend, - }) - - logger.info(f"Writing strategy to `{new_path}`.") - new_path.write_text(strategy_text) else: raise OperationalException("`new-strategy` requires --strategy to be set.") +def deploy_new_hyperopt(hyperopt_name, hyperopt_path: Path, subtemplate: str): + """ + Deploys a new hyperopt template to hyperopt_path + """ + buy_guards = render_template( + templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2",) + sell_guards = render_template( + templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2",) + buy_space = render_template( + templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2",) + sell_space = render_template( + templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2",) + + strategy_text = render_template(templatefile='base_hyperopt.py.j2', + arguments={"hyperopt": hyperopt_name, + "buy_guards": buy_guards, + "sell_guards": sell_guards, + "buy_space": buy_space, + "sell_space": sell_space, + }) + + logger.info(f"Writing hyperopt to `{hyperopt_path}`.") + hyperopt_path.write_text(strategy_text) + + def start_new_hyperopt(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) @@ -135,26 +168,7 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None: if new_path.exists(): raise OperationalException(f"`{new_path}` already exists. " "Please choose another Strategy Name.") - - buy_guards = render_template( - templatefile=f"subtemplates/hyperopt_buy_guards_{args['template']}.j2",) - sell_guards = render_template( - templatefile=f"subtemplates/hyperopt_sell_guards_{args['template']}.j2",) - buy_space = render_template( - templatefile=f"subtemplates/hyperopt_buy_space_{args['template']}.j2",) - sell_space = render_template( - templatefile=f"subtemplates/hyperopt_sell_space_{args['template']}.j2",) - - strategy_text = render_template(templatefile='base_hyperopt.py.j2', - arguments={"hyperopt": args["hyperopt"], - "buy_guards": buy_guards, - "sell_guards": sell_guards, - "buy_space": buy_space, - "sell_space": sell_space, - }) - - logger.info(f"Writing hyperopt to `{new_path}`.") - new_path.write_text(strategy_text) + deploy_new_hyperopt(args['hyperopt'], new_path, args['template']) else: raise OperationalException("`new-hyperopt` requires --hyperopt to be set.") From a6bb7595e850c1c1d0e4cb5bc8401ab250c63138 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 22 Nov 2019 13:44:50 +0100 Subject: [PATCH 31/76] Update utils doc --- docs/utils.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/utils.md b/docs/utils.md index 26d354206..b07008b91 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -59,6 +59,7 @@ freqtrade new-strategy --userdir ~/.freqtrade/ --strategy AwesomeStrategy ``` output usage: freqtrade new-strategy [-h] [--userdir PATH] [-s NAME] + [--template {full,minimal}] optional arguments: -h, --help show this help message and exit @@ -67,6 +68,9 @@ optional arguments: -s NAME, --strategy NAME Specify strategy class name which will be used by the bot. + --template {full,minimal} + Use a template which is either `minimal` or `full` + (containing multiple sample indicators). ``` ## Create new hyperopt @@ -92,6 +96,7 @@ freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt ``` output usage: freqtrade new-hyperopt [-h] [--userdir PATH] [--hyperopt NAME] + [--template {full,minimal}] optional arguments: -h, --help show this help message and exit @@ -99,6 +104,9 @@ optional arguments: Path to userdata directory. --hyperopt NAME Specify hyperopt class name which will be used by the bot. + --template {full,minimal} + Use a template which is either `minimal` or `full` + (containing multiple sample indicators). ``` ## List Exchanges From 097cdcb57ab358dad716b78cf02e481a48e5df35 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 23 Nov 2019 11:32:33 +0300 Subject: [PATCH 32/76] Save epochs at intermediate points --- freqtrade/optimize/hyperopt.py | 46 ++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 6ea2f5133..ecfaba209 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -77,6 +77,8 @@ class Hyperopt: # Previous evaluations self.trials: List = [] + self.num_trials_saved = 0 + # Populate functions here (hasattr is slow so should not be run during "regular" operations) if hasattr(self.custom_hyperopt, 'populate_indicators'): self.backtesting.strategy.advise_indicators = \ @@ -132,13 +134,17 @@ class Hyperopt: arg_dict = {dim.name: value for dim, value in zip(dimensions, params)} return arg_dict - def save_trials(self) -> None: + def save_trials(self, final: bool = False) -> None: """ Save hyperopt trials to file """ - if self.trials: - logger.info("Saving %d evaluations to '%s'", len(self.trials), self.trials_file) + num_trials = len(self.trials) + if num_trials > self.num_trials_saved: + logger.info(f"Saving {num_trials} epochs.") dump(self.trials, self.trials_file) + self.num_trials_saved = num_trials + if final: + logger.info(f"{num_trials} epochs were saved to '{self.trials_file}'.") def read_trials(self) -> List: """ @@ -153,6 +159,12 @@ class Hyperopt: """ Display Best hyperopt result """ + # This is printed when Ctrl+C is pressed quickly, before first epochs have + # a chance to be evaluated. + if not self.trials: + print("No epochs evaluated yet, no best result.") + return + results = sorted(self.trials, key=itemgetter('loss')) best_result = results[0] params = best_result['params'] @@ -197,12 +209,20 @@ class Hyperopt: # Also round to 5 digits after the decimal point print(f"Stoploss: {round(params.get('stoploss'), 5)}") + def is_best(self, results) -> bool: + return results['loss'] < self.current_best_loss + def log_results(self, results) -> None: """ Log results if it is better than any previous evaluation """ print_all = self.config.get('print_all', False) - is_best_loss = results['loss'] < self.current_best_loss + is_best_loss = self.is_best(results) + + if not print_all: + print('.', end='' if results['current_epoch'] % 100 != 0 else None) + sys.stdout.flush() + if print_all or is_best_loss: if is_best_loss: self.current_best_loss = results['loss'] @@ -217,13 +237,9 @@ class Hyperopt: print(log_str) else: print(f'\n{log_str}') - else: - print('.', end='') - sys.stdout.flush() def format_results_logstring(self, results) -> str: - # Output human-friendly index here (starting from 1) - current = results['current_epoch'] + 1 + current = results['current_epoch'] total = self.total_epochs res = results['results_explanation'] loss = results['loss'] @@ -422,15 +438,19 @@ class Hyperopt: self.opt.tell(asked, [v['loss'] for v in f_val]) self.fix_optimizer_models_list() for j in range(jobs): - current = i * jobs + j + # Use human-friendly index here (starting from 1) + current = i * jobs + j + 1 val = f_val[j] val['current_epoch'] = current - val['is_initial_point'] = current < INITIAL_POINTS + val['is_initial_point'] = current <= INITIAL_POINTS + logger.debug(f"Optimizer epoch evaluated: {val}") + is_best = self.is_best(val) self.log_results(val) self.trials.append(val) - logger.debug(f"Optimizer epoch evaluated: {val}") + if is_best or current % 100 == 0: + self.save_trials() except KeyboardInterrupt: print('User interrupted..') - self.save_trials() + self.save_trials(final=True) self.log_trials_result() From 737c07c5b68bb31a22aaa5cc3e3af0de72c45c04 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 23 Nov 2019 11:51:52 +0300 Subject: [PATCH 33/76] Make mypy happy --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index ecfaba209..e536960c4 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -220,7 +220,7 @@ class Hyperopt: is_best_loss = self.is_best(results) if not print_all: - print('.', end='' if results['current_epoch'] % 100 != 0 else None) + print('.', end='' if results['current_epoch'] % 100 != 0 else None) # type: ignore sys.stdout.flush() if print_all or is_best_loss: From 99db53417ccadfc45409eef6bb8301d049ab1f74 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 23 Nov 2019 12:00:43 +0300 Subject: [PATCH 34/76] Tests adjusted --- tests/optimize/test_hyperopt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index e19835e58..00477f790 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -360,7 +360,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: hyperopt.log_results( { 'loss': 1, - 'current_epoch': 1, + 'current_epoch': 2, # This starts from 1 (in a human-friendly manner) 'results_explanation': 'foo.', 'is_initial_point': False } @@ -374,6 +374,7 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: hyperopt.log_results( { 'loss': 3, + 'current_epoch': 1, } ) assert caplog.record_tuples == [] @@ -386,7 +387,7 @@ def test_save_trials_saves_trials(mocker, hyperopt, testdatadir, caplog) -> None hyperopt.save_trials() trials_file = testdatadir / 'optimize' / 'ut_trials.pickle' - assert log_has(f"Saving 1 evaluations to '{trials_file}'", caplog) + assert log_has("Saving 1 epochs.", caplog) mock_dump.assert_called_once() From 067267f4cfc7a0442c4ae400c05be8a47d693333 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 23 Nov 2019 12:20:41 +0300 Subject: [PATCH 35/76] Log messages improved (plural/singular) --- freqtrade/optimize/hyperopt.py | 7 ++++--- tests/optimize/test_hyperopt.py | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index e536960c4..836309a62 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -23,7 +23,7 @@ from skopt import Optimizer from skopt.space import Dimension from freqtrade.data.history import get_timeframe, trim_dataframe -from freqtrade.misc import round_dict +from freqtrade.misc import plural, round_dict from freqtrade.optimize.backtesting import Backtesting # Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F4 @@ -140,11 +140,12 @@ class Hyperopt: """ num_trials = len(self.trials) if num_trials > self.num_trials_saved: - logger.info(f"Saving {num_trials} epochs.") + logger.info(f"Saving {num_trials} {plural(num_trials, 'epoch')}.") dump(self.trials, self.trials_file) self.num_trials_saved = num_trials if final: - logger.info(f"{num_trials} epochs were saved to '{self.trials_file}'.") + logger.info(f"{num_trials} {plural(num_trials, 'epoch')} " + f"saved to '{self.trials_file}'.") def read_trials(self) -> List: """ diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 00477f790..2ec9f5664 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -384,10 +384,11 @@ def test_save_trials_saves_trials(mocker, hyperopt, testdatadir, caplog) -> None trials = create_trials(mocker, hyperopt, testdatadir) mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None) hyperopt.trials = trials - hyperopt.save_trials() + hyperopt.save_trials(final=True) trials_file = testdatadir / 'optimize' / 'ut_trials.pickle' - assert log_has("Saving 1 epochs.", caplog) + assert log_has("Saving 1 epoch.", caplog) + assert log_has(f"1 epoch saved to '{trials_file}'.", caplog) mock_dump.assert_called_once() From 6cb48305343a478105ec7762ce3a5abb8b810a55 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Sat, 23 Nov 2019 12:30:49 +0300 Subject: [PATCH 36/76] Testcase added --- tests/optimize/test_hyperopt.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 2ec9f5664..d3d544502 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -383,14 +383,19 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: def test_save_trials_saves_trials(mocker, hyperopt, testdatadir, caplog) -> None: trials = create_trials(mocker, hyperopt, testdatadir) mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None) + trials_file = testdatadir / 'optimize' / 'ut_trials.pickle' + hyperopt.trials = trials hyperopt.save_trials(final=True) - - trials_file = testdatadir / 'optimize' / 'ut_trials.pickle' assert log_has("Saving 1 epoch.", caplog) assert log_has(f"1 epoch saved to '{trials_file}'.", caplog) mock_dump.assert_called_once() + hyperopt.trials = trials + trials + hyperopt.save_trials(final=True) + assert log_has("Saving 2 epochs.", caplog) + assert log_has(f"2 epochs saved to '{trials_file}'.", caplog) + def test_read_trials_returns_trials_file(mocker, hyperopt, testdatadir, caplog) -> None: trials = create_trials(mocker, hyperopt, testdatadir) From 5fb14e769b69e790a3a0f24820b24c690415c7bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 14:52:44 +0100 Subject: [PATCH 37/76] Adjust folder to match user_data folder - otherwise running tests creates this folder --- tests/test_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index ec4df9125..31502cafc 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -212,9 +212,9 @@ def test_generate_plot_file(mocker, caplog): fig = generate_empty_figure() plot_mock = mocker.patch("freqtrade.plot.plotting.plot", MagicMock()) store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html", - directory=Path("user_data/plots")) + directory=Path("user_data/plot")) - expected_fn = str(Path("user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html")) + expected_fn = str(Path("user_data/plot/freqtrade-plot-UNITTEST_BTC-5m.html")) assert plot_mock.call_count == 1 assert plot_mock.call_args[0][0] == fig assert (plot_mock.call_args_list[0][1]['filename'] From 63ad95a474b54eeab12b4f874c5dfbb5771657e6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 20:13:20 +0100 Subject: [PATCH 38/76] reenable slack --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c74b1720e..99f3b3eec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,6 +74,7 @@ jobs: # 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"} + export CI_BRANCH=${HEAD_REF} echo "${CI_BRANCH}" coveralls || true @@ -97,7 +98,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: always() && github.repository.fork == true + if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: type: ${{ job.status }} job_name: '*Freqtrade CI ${{ matrix.os }}*' @@ -158,7 +159,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: always() && github.repository.fork == true + if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: type: ${{ job.status }} job_name: '*Freqtrade CI windows*' @@ -178,7 +179,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: failure() && github.repository.fork == true + if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: type: ${{ job.status }} job_name: '*Freqtrade Docs*' @@ -219,7 +220,7 @@ jobs: - name: Slack Notification uses: homoluctus/slatify@v1.8.0 - if: always() && github.repository.fork == true + if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: type: ${{ job.status }} job_name: '*Freqtrade CI Deploy*' From f05818a86e64f7194a24dc74451cb9b1641e3de6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 19:48:15 +0100 Subject: [PATCH 39/76] Allow transition from "no-config"-pairlist to pairlists --- freqtrade/configuration/deprecated_settings.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 8f3dbd675..b1e3535a3 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -58,6 +58,13 @@ 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 not config.get('pairlists') and not config.get('pairlists'): + config['pairlists'] = [{'method': 'StaticPairList'}] + logger.warning( + "DEPRECATED: " + "Pairlists must be defined explicitly in the future." + "Defaulting to StaticPairList for now.") + if config.get('pairlist', {}).get("method") == 'VolumePairList': logger.warning( "DEPRECATED: " From cbf710a4f86f6d54ff56deb1f3f83fabbc592425 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 19:48:33 +0100 Subject: [PATCH 40/76] Fix coveralls (?) --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99f3b3eec..ea5f9c395 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,9 +73,9 @@ 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"} - export CI_BRANCH=${HEAD_REF} - echo "${CI_BRANCH}" + export TRAVIS_BRANCH=${GITHUB_REF#"ref/heads"} + export TRAVIS_BRANCH=${HEAD_REF} + echo "${TRAVIS_BRANCH}" coveralls || true - name: Backtesting From a374df76225cdb7f4a3bc93462336708ad650e8f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Nov 2019 09:55:34 +0100 Subject: [PATCH 41/76] some minor fixes from feedback --- docs/strategy-customization.md | 2 +- docs/utils.md | 7 +++++-- freqtrade/configuration/cli_options.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 352389d5e..c43d8e3f6 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -22,7 +22,7 @@ The bot includes a default strategy file. Also, several other strategies are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). You will however most likely have your own idea for a strategy. -This Document intends to help you develop one for yourself. +This document intends to help you develop one for yourself. To get started, use `freqtrade new-strategy --strategy AwesomeStrategy`. This will create a new strategy file from a template, which will be located under `user_data/strategies/AwesomeStrategy.py`. diff --git a/docs/utils.md b/docs/utils.md index b07008b91..ca4b645a5 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -70,7 +70,9 @@ optional arguments: bot. --template {full,minimal} Use a template which is either `minimal` or `full` - (containing multiple sample indicators). + (containing multiple sample indicators). Default: + `full`. + ``` ## Create new hyperopt @@ -106,7 +108,8 @@ optional arguments: bot. --template {full,minimal} Use a template which is either `minimal` or `full` - (containing multiple sample indicators). + (containing multiple sample indicators). Default: + `full`. ``` ## List Exchanges diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index be9397975..2061534e7 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -343,7 +343,7 @@ AVAILABLE_CLI_OPTIONS = { "template": Arg( '--template', help='Use a template which is either `minimal` or ' - '`full` (containing multiple sample indicators).', + '`full` (containing multiple sample indicators). Default: `%(default)s`.', choices=['full', 'minimal'], default='full', ), From 8c64be3cfd618ba3e69dc30c1cb4096d4a56b693 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2019 07:01:23 +0100 Subject: [PATCH 42/76] get tickers only once to show balance --- freqtrade/rpc/rpc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 8898f3068..f2851e0e1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -301,6 +301,7 @@ class RPC: """ Returns current account balance per crypto """ output = [] total = 0.0 + tickers = self._freqtrade.exchange.get_tickers() for coin, balance in self._freqtrade.exchange.get_balances().items(): if not balance['total']: continue @@ -310,10 +311,11 @@ class RPC: else: try: pair = self._freqtrade.exchange.get_valid_pair_combination(coin, "BTC") + if pair.startswith("BTC"): - rate = 1.0 / self._freqtrade.get_sell_rate(pair, False) + rate = 1.0 / tickers.get(pair, {}).get('bid', 1) else: - rate = self._freqtrade.get_sell_rate(pair, False) + rate = tickers.get(pair, {}).get('bid', 1) except (TemporaryError, DependencyException): logger.warning(f" Could not get rate for pair {coin}.") continue From 62d50f512d82c9988897425b5013f718ae039f34 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2019 20:12:41 +0100 Subject: [PATCH 43/76] add tests for balance from get-tickers --- freqtrade/rpc/rpc.py | 6 +++- tests/conftest.py | 50 +++++++++++++++++++++++++++++++-- tests/pairlist/test_pairlist.py | 16 +++++------ tests/rpc/test_rpc.py | 38 +++++++++---------------- tests/rpc/test_rpc_telegram.py | 23 ++------------- 5 files changed, 75 insertions(+), 58 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f2851e0e1..e338cd6dd 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -301,7 +301,11 @@ class RPC: """ Returns current account balance per crypto """ output = [] total = 0.0 - tickers = self._freqtrade.exchange.get_tickers() + try: + tickers = self._freqtrade.exchange.get_tickers() + except (TemporaryError, DependencyException): + raise RPCException('Error getting current tickers.') + for coin, balance in self._freqtrade.exchange.get_balances().items(): if not balance['total']: continue diff --git a/tests/conftest.py b/tests/conftest.py index fbd23a0dc..bc6599e4d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -980,6 +980,28 @@ def tickers(): 'quoteVolume': 62.68220262, 'info': {} }, + 'BTC/USDT': { + 'symbol': 'BTC/USDT', + 'timestamp': 1573758371399, + 'datetime': '2019-11-14T19:06:11.399Z', + 'high': 8800.0, + 'low': 8582.6, + 'bid': 8648.16, + 'bidVolume': 0.238771, + 'ask': 8648.72, + 'askVolume': 0.016253, + 'vwap': 8683.13647806, + 'open': 8759.7, + 'close': 8648.72, + 'last': 8648.72, + 'previousClose': 8759.67, + 'change': -110.98, + 'percentage': -1.267, + 'average': None, + 'baseVolume': 35025.943355, + 'quoteVolume': 304135046.4242901, + 'info': {} + }, 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, @@ -1067,7 +1089,29 @@ def tickers(): 'baseVolume': 59698.79897, 'quoteVolume': 29132399.743954, 'info': {} - } + }, + 'XRP/BTC': { + 'symbol': 'XRP/BTC', + 'timestamp': 1573758257534, + 'datetime': '2019-11-14T19:04:17.534Z', + 'high': 3.126e-05, + 'low': 3.061e-05, + 'bid': 3.093e-05, + 'bidVolume': 27901.0, + 'ask': 3.095e-05, + 'askVolume': 10551.0, + 'vwap': 3.091e-05, + 'open': 3.119e-05, + 'close': 3.094e-05, + 'last': 3.094e-05, + 'previousClose': 3.117e-05, + 'change': -2.5e-07, + 'percentage': -0.802, + 'average': None, + 'baseVolume': 37334921.0, + 'quoteVolume': 1154.19266394, + 'info': {} + }, }) @@ -1317,8 +1361,8 @@ def rpc_balance(): 'used': 0.0 }, 'XRP': { - 'total': 1.0, - 'free': 1.0, + 'total': 0.1, + 'free': 0.01, 'used': 0.0 }, 'EUR': { diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 76537880c..460f2ddcf 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -100,7 +100,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', 'FUEL/BTC'] + whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC'] bot.pairlists.refresh_pairlist() assert whitelist == bot.pairlists.whitelist @@ -135,10 +135,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): @pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [ ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']), # Different sorting depending on quote or bid volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], - "BTC", ['HOT/BTC', 'FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + "BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], "USDT", ['ETH/USDT']), # No pair for ETH ... @@ -146,19 +146,19 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf): "ETH", []), # Precisionfilter and quote volume ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, - {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), + {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # Precisionfilter bid ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, - {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), + {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']), # PriceFilter and VolumePairList ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "PriceFilter", "low_price_ratio": 0.03}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), + "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # Hot is removed by precision_filter, Fuel by low_price_filter. - ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, + ([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"}, {"method": "PrecisionFilter"}, {"method": "PriceFilter", "low_price_ratio": 0.02} - ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), + ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']), # StaticPairlist Only ([{"method": "StaticPairList"}, ], "BTC", ['ETH/BTC', 'TKN/BTC']), diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index fb7a5276a..d745212ac 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -355,29 +355,18 @@ def test_rpc_balance_handle_error(default_conf, mocker): mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=mock_balance), - get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) + get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() - - result = rpc._rpc_balance(default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 12) - assert prec_satoshi(result['value'], 180000) - assert 'USD' == result['symbol'] - assert result['currencies'] == [{ - 'currency': 'BTC', - 'free': 10.0, - 'balance': 12.0, - 'used': 2.0, - 'est_btc': 12.0, - }] - assert result['total'] == 12.0 + with pytest.raises(RPCException, match="Error getting current tickers."): + rpc._rpc_balance(default_conf['fiat_display_currency']) -def test_rpc_balance_handle(default_conf, mocker): +def test_rpc_balance_handle(default_conf, mocker, tickers): mock_balance = { 'BTC': { 'free': 10.0, @@ -389,7 +378,7 @@ def test_rpc_balance_handle(default_conf, mocker): 'total': 5.0, 'used': 4.0, }, - 'PAX': { + 'USDT': { 'free': 5.0, 'total': 10.0, 'used': 5.0, @@ -405,10 +394,9 @@ def test_rpc_balance_handle(default_conf, mocker): mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value=mock_balance), - get_ticker=MagicMock( - side_effect=lambda p, r: {'bid': 100} if p == "BTC/PAX" else {'bid': 0.01}), + get_tickers=tickers, get_valid_pair_combination=MagicMock( - side_effect=lambda a, b: f"{b}/{a}" if a == "PAX" else f"{a}/{b}") + side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}") ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) @@ -417,8 +405,8 @@ def test_rpc_balance_handle(default_conf, mocker): rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 12.15) - assert prec_satoshi(result['value'], 182250) + assert prec_satoshi(result['total'], 12.309096315) + assert prec_satoshi(result['value'], 184636.44472997) assert 'USD' == result['symbol'] assert result['currencies'] == [ {'currency': 'BTC', @@ -430,16 +418,16 @@ def test_rpc_balance_handle(default_conf, mocker): {'free': 1.0, 'balance': 5.0, 'currency': 'ETH', - 'est_btc': 0.05, + 'est_btc': 0.30794, 'used': 4.0 }, {'free': 5.0, 'balance': 10.0, - 'currency': 'PAX', - 'est_btc': 0.1, + 'currency': 'USDT', + 'est_btc': 0.0011563153318162476, 'used': 5.0} ] - assert result['total'] == 12.15 + assert result['total'] == 12.309096315331816 def test_rpc_start(mocker, default_conf) -> None: diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index a33ab8675..89fd90b0b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -461,29 +461,10 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] -def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance) -> None: - - def mock_ticker(symbol, refresh): - if symbol == 'BTC/USDT': - return { - 'bid': 10000.00, - 'ask': 10000.00, - 'last': 10000.00, - } - elif symbol == 'XRP/BTC': - return { - 'bid': 0.00001, - 'ask': 0.00001, - 'last': 0.00001, - } - return { - 'bid': 0.1, - 'ask': 0.1, - 'last': 0.1, - } +def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) - mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) + mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") From 1bf8d8cff320da2b8e05fd64102faaf33e786e2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 15 Nov 2019 06:33:07 +0100 Subject: [PATCH 44/76] show /balance in stake currency --- freqtrade/rpc/api_server.py | 3 ++- freqtrade/rpc/rpc.py | 24 +++++++++++++----------- freqtrade/rpc/telegram.py | 7 ++++--- tests/rpc/test_rpc.py | 25 +++++++++++++++---------- tests/rpc/test_rpc_apiserver.py | 3 ++- tests/rpc/test_rpc_telegram.py | 3 ++- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index f87165253..4baca7f22 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -354,7 +354,8 @@ class ApiServer(RPC): Returns the current status of the trades in json format """ - results = self._rpc_balance(self._config.get('fiat_display_currency', '')) + results = self._rpc_balance(self._config['stake_currency'], + self._config.get('fiat_display_currency', '')) return self.rest_dump(results) @require_login diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index e338cd6dd..137e72ea6 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -297,7 +297,7 @@ class RPC: 'best_rate': round(bp_rate * 100, 2), } - def _rpc_balance(self, fiat_display_currency: str) -> Dict: + def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict: """ Returns current account balance per crypto """ output = [] total = 0.0 @@ -310,27 +310,29 @@ class RPC: if not balance['total']: continue - if coin == 'BTC': + est_stake: float = 0 + if coin == stake_currency: rate = 1.0 + est_stake = balance['total'] else: try: - pair = self._freqtrade.exchange.get_valid_pair_combination(coin, "BTC") - - if pair.startswith("BTC"): - rate = 1.0 / tickers.get(pair, {}).get('bid', 1) - else: - rate = tickers.get(pair, {}).get('bid', 1) + pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) + rate = tickers.get(pair, {}).get('bid', None) + if rate: + if pair.startswith(stake_currency): + rate = 1.0 / rate + est_stake = rate * balance['total'] except (TemporaryError, DependencyException): logger.warning(f" Could not get rate for pair {coin}.") continue - est_btc: float = rate * balance['total'] - total = total + est_btc + total = total + (est_stake or 0) output.append({ 'currency': coin, 'free': balance['free'] if balance['free'] is not None else 0, 'balance': balance['total'] if balance['total'] is not None else 0, 'used': balance['used'] if balance['used'] is not None else 0, - 'est_btc': est_btc, + 'est_stake': est_stake or 0, + 'stake': stake_currency, }) if total == 0.0: if self._freqtrade.config.get('dry_run', False): diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0547af7b0..2ae22f472 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -325,15 +325,16 @@ class Telegram(RPC): def _balance(self, update: Update, context: CallbackContext) -> None: """ Handler for /balance """ try: - result = self._rpc_balance(self._config.get('fiat_display_currency', '')) + result = self._rpc_balance(self._config['stake_currency'], + self._config.get('fiat_display_currency', '')) output = '' for currency in result['currencies']: - if currency['est_btc'] > 0.0001: + if currency['est_stake'] > 0.0001: curr_output = "*{currency}:*\n" \ "\t`Available: {free: .8f}`\n" \ "\t`Balance: {balance: .8f}`\n" \ "\t`Pending: {used: .8f}`\n" \ - "\t`Est. BTC: {est_btc: .8f}`\n".format(**currency) + "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency) else: curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d745212ac..2c7228274 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -363,7 +363,7 @@ def test_rpc_balance_handle_error(default_conf, mocker): rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() with pytest.raises(RPCException, match="Error getting current tickers."): - rpc._rpc_balance(default_conf['fiat_display_currency']) + rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) def test_rpc_balance_handle(default_conf, mocker, tickers): @@ -404,28 +404,33 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() - result = rpc._rpc_balance(default_conf['fiat_display_currency']) + result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) assert prec_satoshi(result['total'], 12.309096315) assert prec_satoshi(result['value'], 184636.44472997) assert 'USD' == result['symbol'] assert result['currencies'] == [ {'currency': 'BTC', - 'free': 10.0, - 'balance': 12.0, - 'used': 2.0, - 'est_btc': 12.0, + 'free': 10.0, + 'balance': 12.0, + 'used': 2.0, + 'est_stake': 12.0, + 'stake': 'BTC', }, {'free': 1.0, 'balance': 5.0, 'currency': 'ETH', - 'est_btc': 0.30794, - 'used': 4.0 + 'est_stake': 0.30794, + 'used': 4.0, + 'stake': 'BTC', + }, {'free': 5.0, 'balance': 10.0, 'currency': 'USDT', - 'est_btc': 0.0011563153318162476, - 'used': 5.0} + 'est_stake': 0.0011563153318162476, + 'used': 5.0, + 'stake': 'BTC', + } ] assert result['total'] == 12.309096315331816 diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8eff37023..4dc3fd265 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -256,7 +256,8 @@ def test_api_balance(botclient, mocker, rpc_balance): 'free': 12.0, 'balance': 12.0, 'used': 0.0, - 'est_btc': 12.0, + 'est_stake': 12.0, + 'stake': 'BTC', } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 89fd90b0b..c848a3efd 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -545,7 +545,8 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None 'free': 1.0, 'used': 0.5, 'balance': i, - 'est_btc': 1 + 'est_stake': 1, + 'stake': 'BTC', }) mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={ 'currencies': balances, From 50350a09cd494c802a1586cfaec9bf78ef876c4f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Nov 2019 19:41:51 +0100 Subject: [PATCH 45/76] use wallets instead of doing a direct call to /balance --- freqtrade/rpc/rpc.py | 14 +++++++------- freqtrade/wallets.py | 5 ++++- tests/rpc/test_rpc_apiserver.py | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 137e72ea6..4cebe646e 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -306,14 +306,14 @@ class RPC: except (TemporaryError, DependencyException): raise RPCException('Error getting current tickers.') - for coin, balance in self._freqtrade.exchange.get_balances().items(): - if not balance['total']: + for coin, balance in self._freqtrade.wallets.get_all_balances().items(): + if not balance.total: continue est_stake: float = 0 if coin == stake_currency: rate = 1.0 - est_stake = balance['total'] + est_stake = balance.total else: try: pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) @@ -321,16 +321,16 @@ class RPC: if rate: if pair.startswith(stake_currency): rate = 1.0 / rate - est_stake = rate * balance['total'] + est_stake = rate * balance.total except (TemporaryError, DependencyException): logger.warning(f" Could not get rate for pair {coin}.") continue total = total + (est_stake or 0) output.append({ 'currency': coin, - 'free': balance['free'] if balance['free'] is not None else 0, - 'balance': balance['total'] if balance['total'] is not None else 0, - 'used': balance['used'] if balance['used'] is not None else 0, + 'free': balance.free if balance.free is not None else 0, + 'balance': balance.total if balance.total is not None else 0, + 'used': balance.used if balance.used is not None else 0, 'est_stake': est_stake or 0, 'stake': stake_currency, }) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 90b68c49d..c674b5286 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -2,7 +2,7 @@ """ Wallet """ import logging -from typing import Dict, NamedTuple +from typing import Dict, NamedTuple, Any from freqtrade.exchange import Exchange from freqtrade import constants @@ -72,3 +72,6 @@ class Wallets: ) logger.info('Wallets synced.') + + def get_all_balances(self) -> Dict[str, Any]: + return self._wallets diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 4dc3fd265..7b3e787f4 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -243,9 +243,9 @@ def test_api_balance(botclient, mocker, rpc_balance): 'last': 0.1, } mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) - mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") + ftbot.wallets.update() rc = client_get(client, f"{BASE_URI}/balance") assert_response(rc) From 1b337fe5e1c1cc67752aa8af6b0bf7f612038d96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Nov 2019 19:47:20 +0100 Subject: [PATCH 46/76] Remove unnecessary code piece --- tests/rpc/test_rpc_apiserver.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 7b3e787f4..8d5b4a6b8 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -224,24 +224,6 @@ def test_api_stopbuy(botclient): def test_api_balance(botclient, mocker, rpc_balance): ftbot, client = botclient - def mock_ticker(symbol, refresh): - if symbol == 'BTC/USDT': - return { - 'bid': 10000.00, - 'ask': 10000.00, - 'last': 10000.00, - } - elif symbol == 'XRP/BTC': - return { - 'bid': 0.00001, - 'ask': 0.00001, - 'last': 0.00001, - } - return { - 'bid': 0.1, - 'ask': 0.1, - 'last': 0.1, - } mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") From a3415e52c0311e901be1267925ac26f23601ad0c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 15:20:53 +0100 Subject: [PATCH 47/76] Fix some test-types --- tests/rpc/test_rpc.py | 2 +- tests/rpc/test_rpc_apiserver.py | 18 ++++++++++++------ tests/rpc/test_rpc_manager.py | 5 ++++- tests/rpc/test_rpc_telegram.py | 4 ++-- tests/rpc/test_rpc_webhook.py | 2 +- tests/test_freqtradebot.py | 11 ++++++----- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index fb7a5276a..0e8588aea 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -697,8 +697,8 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None pair = 'XRP/BTC' # Test not buying - default_conf['stake_amount'] = 0.0000001 freqtradebot = get_patched_freqtradebot(mocker, default_conf) + freqtradebot.config['stake_amount'] = 0.0000001 patch_get_signal(freqtradebot, (True, False)) rpc = RPC(freqtradebot) pair = 'TKN/BTC' diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8eff37023..a59fd1942 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -23,7 +23,7 @@ _TEST_PASS = "SuperSecurePassword1!" def botclient(default_conf, mocker): default_conf.update({"api_server": {"enabled": True, "listen_ip_address": "127.0.0.1", - "listen_port": "8080", + "listen_port": 8080, "username": _TEST_USER, "password": _TEST_PASS, }}) @@ -133,7 +133,10 @@ def test_api__init__(default_conf, mocker): def test_api_run(default_conf, mocker, caplog): default_conf.update({"api_server": {"enabled": True, "listen_ip_address": "127.0.0.1", - "listen_port": "8080"}}) + "listen_port": 8080, + "username": "TestUser", + "password": "testPass", + }}) mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock()) @@ -146,7 +149,7 @@ def test_api_run(default_conf, mocker, caplog): apiserver.run() assert server_mock.call_count == 1 assert server_mock.call_args_list[0][0][0] == "127.0.0.1" - assert server_mock.call_args_list[0][0][1] == "8080" + assert server_mock.call_args_list[0][0][1] == 8080 assert isinstance(server_mock.call_args_list[0][0][2], Flask) assert hasattr(apiserver, "srv") @@ -158,14 +161,14 @@ def test_api_run(default_conf, mocker, caplog): server_mock.reset_mock() apiserver._config.update({"api_server": {"enabled": True, "listen_ip_address": "0.0.0.0", - "listen_port": "8089", + "listen_port": 8089, "password": "", }}) apiserver.run() assert server_mock.call_count == 1 assert server_mock.call_args_list[0][0][0] == "0.0.0.0" - assert server_mock.call_args_list[0][0][1] == "8089" + assert server_mock.call_args_list[0][0][1] == 8089 assert isinstance(server_mock.call_args_list[0][0][2], Flask) assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog) assert log_has("Starting Local Rest Server.", caplog) @@ -186,7 +189,10 @@ def test_api_run(default_conf, mocker, caplog): def test_api_cleanup(default_conf, mocker, caplog): default_conf.update({"api_server": {"enabled": True, "listen_ip_address": "127.0.0.1", - "listen_port": "8080"}}) + "listen_port": 8080, + "username": "TestUser", + "password": "testPass", + }}) mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock()) mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock()) diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py index c9fbf8c3b..edf6bae4d 100644 --- a/tests/rpc/test_rpc_manager.py +++ b/tests/rpc/test_rpc_manager.py @@ -173,7 +173,10 @@ def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None: default_conf["telegram"]["enabled"] = False default_conf["api_server"] = {"enabled": True, "listen_ip_address": "127.0.0.1", - "listen_port": "8080"} + "listen_port": 8080, + "username": "TestUser", + "password": "TestPass", + } rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) # Sleep to allow the thread to start diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index a33ab8675..f69c2ac38 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -144,9 +144,9 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None: def test_status(default_conf, update, mocker, fee, ticker,) -> None: - update.message.chat.id = 123 + update.message.chat.id = "123" default_conf['telegram']['enabled'] = False - default_conf['telegram']['chat_id'] = 123 + default_conf['telegram']['chat_id'] = "123" mocker.patch.multiple( 'freqtrade.exchange.Exchange', diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index dbbc4cefb..c066aa8e7 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -113,7 +113,7 @@ def test_send_msg(default_conf, mocker): def test_exception_send_msg(default_conf, mocker, caplog): default_conf["webhook"] = get_webhook_dict() - default_conf["webhook"]["webhookbuy"] = None + del default_conf["webhook"]["webhookbuy"] webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION}) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index b01c8e247..76a50f0f4 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -299,7 +299,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, limit_buy_order, fee) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - default_conf['stake_amount'] = 0.0000098751 + default_conf['stake_amount'] = 0.0098751 default_conf['max_open_trades'] = 2 mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -313,7 +313,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, trade = Trade.query.first() assert trade is not None - assert trade.stake_amount == 0.0000098751 + assert trade.stake_amount == 0.0098751 assert trade.is_open assert trade.open_date is not None @@ -321,11 +321,11 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, trade = Trade.query.order_by(Trade.id.desc()).first() assert trade is not None - assert trade.stake_amount == 0.0000098751 + assert trade.stake_amount == 0.0098751 assert trade.is_open assert trade.open_date is not None - assert Trade.total_open_trades_stakes() == 1.97502e-05 + assert Trade.total_open_trades_stakes() == 1.97502e-02 def test_get_min_pair_stake_amount(mocker, default_conf) -> None: @@ -522,8 +522,9 @@ def test_create_trades_too_small_stake_amount(default_conf, ticker, limit_buy_or get_fee=fee, ) - default_conf['stake_amount'] = 0.000000005 freqtrade = FreqtradeBot(default_conf) + freqtrade.config['stake_amount'] = 0.000000005 + patch_get_signal(freqtrade) assert not freqtrade.create_trades() From 4dc0631a4b8aa8e65ca7d85690f3e5b6dd560211 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 15:41:09 +0100 Subject: [PATCH 48/76] Lower minimum tradeable value --- freqtrade/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index bf5d822c6..c16850c38 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -71,7 +71,7 @@ CONF_SCHEMA = { 'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']}, 'stake_amount': { "type": ["number", "string"], - "minimum": 0.0005, + "minimum": 0.0001, "pattern": UNLIMITED_STAKE_AMOUNT }, 'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT}, From af3eea38055acdcf533f9810d282205bc315399e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 15:49:46 +0100 Subject: [PATCH 49/76] Move config json validation to after strategy loading Otherwise attributes are mandatory in configuration while they could be set in the strategy --- freqtrade/configuration/config_validation.py | 5 +++++ freqtrade/configuration/configuration.py | 8 -------- freqtrade/optimize/backtesting.py | 5 ++++- freqtrade/optimize/edge_cli.py | 5 ++++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 8a7641a08..bfba59385 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -61,6 +61,11 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None: :param conf: Config in JSON format :return: Returns None if everything is ok, otherwise throw an OperationalException """ + + # validate configuration before returning + logger.info('Validating configuration ...') + validate_config_schema(conf) + # validating trailing stoploss _validate_trailing_stoploss(conf) _validate_edge(conf) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 93eee3912..277bf8da9 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -9,8 +9,6 @@ from typing import Any, Callable, Dict, List, Optional from freqtrade import OperationalException, constants from freqtrade.configuration.check_exchange import check_exchange -from freqtrade.configuration.config_validation import (validate_config_consistency, - validate_config_schema) from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings from freqtrade.configuration.directory_operations import (create_datadir, create_userdata_dir) @@ -84,10 +82,6 @@ class Configuration: if 'pairlists' not in config: config['pairlists'] = [] - # validate configuration before returning - logger.info('Validating configuration ...') - validate_config_schema(config) - return config def load_config(self) -> Dict[str, Any]: @@ -118,8 +112,6 @@ class Configuration: process_temporary_deprecated_settings(config) - validate_config_consistency(config) - return config def _process_logging_options(self, config: Dict[str, Any]) -> None: diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 2c2d116a4..d9fb1f2d1 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -13,7 +13,8 @@ from pandas import DataFrame from tabulate import tabulate from freqtrade import OperationalException -from freqtrade.configuration import TimeRange, remove_credentials +from freqtrade.configuration import (TimeRange, remove_credentials, + validate_config_consistency) from freqtrade.data import history from freqtrade.data.dataprovider import DataProvider from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds @@ -75,10 +76,12 @@ class Backtesting: stratconf = deepcopy(self.config) stratconf['strategy'] = strat self.strategylist.append(StrategyResolver(stratconf).strategy) + validate_config_consistency(stratconf) else: # No strategy list specified, only one strategy self.strategylist.append(StrategyResolver(self.config).strategy) + validate_config_consistency(self.config) if "ticker_interval" not in self.config: raise OperationalException("Ticker-interval needs to be set in either configuration " diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 5a4543884..a667ebb92 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -9,7 +9,8 @@ from typing import Any, Dict from tabulate import tabulate from freqtrade import constants -from freqtrade.configuration import TimeRange, remove_credentials +from freqtrade.configuration import (TimeRange, remove_credentials, + validate_config_consistency) from freqtrade.edge import Edge from freqtrade.exchange import Exchange from freqtrade.resolvers import StrategyResolver @@ -35,6 +36,8 @@ class EdgeCli: self.exchange = Exchange(self.config) self.strategy = StrategyResolver(self.config).strategy + validate_config_consistency(self.config) + self.edge = Edge(config, self.exchange, self.strategy) # Set refresh_pairs to false for edge-cli (it must be true for edge) self.edge._refresh_pairs = False From 8d002a8f28cadf73df6f496bf3b2f0e29cfa3870 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Nov 2019 15:50:23 +0100 Subject: [PATCH 50/76] Fix some more tests --- tests/pairlist/test_pairlist.py | 5 ----- tests/test_configuration.py | 4 ---- 2 files changed, 9 deletions(-) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index 76537880c..32d66d3e8 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -285,12 +285,7 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers): 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'] = [] diff --git a/tests/test_configuration.py b/tests/test_configuration.py index e971d15ab..e50ba99ee 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -100,7 +100,6 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: assert validated_conf['max_open_trades'] == 0 assert 'internals' in validated_conf - assert log_has('Validating configuration ...', caplog) def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None: @@ -132,7 +131,6 @@ def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None: assert validated_conf['exchange']['pair_whitelist'] == conf2['exchange']['pair_whitelist'] assert 'internals' in validated_conf - assert log_has('Validating configuration ...', caplog) def test_from_config(default_conf, mocker, caplog) -> None: @@ -159,7 +157,6 @@ def test_from_config(default_conf, mocker, caplog) -> None: assert validated_conf['exchange']['pair_whitelist'] == conf2['exchange']['pair_whitelist'] assert validated_conf['fiat_display_currency'] == "EUR" assert 'internals' in validated_conf - assert log_has('Validating configuration ...', caplog) assert isinstance(validated_conf['user_data_dir'], Path) @@ -191,7 +188,6 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> assert validated_conf['max_open_trades'] > 999999999 assert validated_conf['max_open_trades'] == float('inf') - assert log_has('Validating configuration ...', caplog) assert "runmode" in validated_conf assert validated_conf['runmode'] == RunMode.DRY_RUN From e7be742c58c9d4e1e3b15e7e4dc8f6979b96836a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 07:05:18 +0100 Subject: [PATCH 51/76] Run validation after custom validations --- freqtrade/configuration/config_validation.py | 8 ++++---- freqtrade/constants.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index bfba59385..4bfd24677 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -62,15 +62,15 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None: :return: Returns None if everything is ok, otherwise throw an OperationalException """ - # validate configuration before returning - logger.info('Validating configuration ...') - validate_config_schema(conf) - # validating trailing stoploss _validate_trailing_stoploss(conf) _validate_edge(conf) _validate_whitelist(conf) + # validate configuration before returning + logger.info('Validating configuration ...') + validate_config_schema(conf) + def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None: diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c16850c38..22dcc9755 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -66,7 +66,7 @@ MINIMAL_CONFIG = { CONF_SCHEMA = { 'type': 'object', 'properties': { - 'max_open_trades': {'type': 'integer', 'minimum': -1}, + 'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1}, 'ticker_interval': {'type': 'string', 'enum': TIMEFRAMES}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']}, 'stake_amount': { From 646a9d12b20604a5536800af2a34ed667147f56f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 07:06:55 +0100 Subject: [PATCH 52/76] Align quoting of json schema --- freqtrade/constants.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 22dcc9755..58eb304a9 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -70,9 +70,9 @@ CONF_SCHEMA = { 'ticker_interval': {'type': 'string', 'enum': TIMEFRAMES}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']}, 'stake_amount': { - "type": ["number", "string"], - "minimum": 0.0001, - "pattern": UNLIMITED_STAKE_AMOUNT + 'type': ['number', 'string'], + 'minimum': 0.0001, + 'pattern': UNLIMITED_STAKE_AMOUNT }, 'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT}, 'dry_run': {'type': 'boolean'}, @@ -197,8 +197,8 @@ CONF_SCHEMA = { 'listen_ip_address': {'format': 'ipv4'}, 'listen_port': { 'type': 'integer', - "minimum": 1024, - "maximum": 65535 + 'minimum': 1024, + 'maximum': 65535 }, 'username': {'type': 'string'}, 'password': {'type': 'string'}, @@ -253,19 +253,19 @@ CONF_SCHEMA = { 'edge': { 'type': 'object', 'properties': { - "enabled": {'type': 'boolean'}, - "process_throttle_secs": {'type': 'integer', 'minimum': 600}, - "calculate_since_number_of_days": {'type': 'integer'}, - "allowed_risk": {'type': 'number'}, - "capital_available_percentage": {'type': 'number'}, - "stoploss_range_min": {'type': 'number'}, - "stoploss_range_max": {'type': 'number'}, - "stoploss_range_step": {'type': 'number'}, - "minimum_winrate": {'type': 'number'}, - "minimum_expectancy": {'type': 'number'}, - "min_trade_number": {'type': 'number'}, - "max_trade_duration_minute": {'type': 'integer'}, - "remove_pumps": {'type': 'boolean'} + 'enabled': {'type': 'boolean'}, + 'process_throttle_secs': {'type': 'integer', 'minimum': 600}, + 'calculate_since_number_of_days': {'type': 'integer'}, + 'allowed_risk': {'type': 'number'}, + 'capital_available_percentage': {'type': 'number'}, + 'stoploss_range_min': {'type': 'number'}, + 'stoploss_range_max': {'type': 'number'}, + 'stoploss_range_step': {'type': 'number'}, + 'minimum_winrate': {'type': 'number'}, + 'minimum_expectancy': {'type': 'number'}, + 'min_trade_number': {'type': 'number'}, + 'max_trade_duration_minute': {'type': 'integer'}, + 'remove_pumps': {'type': 'boolean'} }, 'required': ['process_throttle_secs', 'allowed_risk', 'capital_available_percentage'] } From 0775ac081a05a32446bcab3d30420b2874da1b63 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 07:12:30 +0100 Subject: [PATCH 53/76] Cleanup constants and required --- freqtrade/constants.py | 2 +- freqtrade/rpc/api_server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 58eb304a9..b485ba0d8 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -6,7 +6,6 @@ bot constants DEFAULT_CONFIG = 'config.json' DEFAULT_EXCHANGE = 'bittrex' PROCESS_THROTTLE_SECS = 5 # sec -DEFAULT_TICKER_INTERVAL = 5 # min HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss' @@ -280,5 +279,6 @@ CONF_SCHEMA = { 'dry_run', 'bid_strategy', 'unfilledtimeout', + 'stoploss', ] } diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index f87165253..1ec8cc305 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -312,7 +312,7 @@ class ApiServer(RPC): logger.info("LocalRPC - Profit Command Called") stats = self._rpc_trade_statistics(self._config['stake_currency'], - self._config['fiat_display_currency'] + self._config.get('fiat_display_currency') ) return self.rest_dump(stats) From d1511a108577629b8df45e36d185ffabeb5e29b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 07:12:39 +0100 Subject: [PATCH 54/76] Update some config documentation --- docs/configuration.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f913d7dbb..bf7c07268 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,12 +40,12 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Command | Default | Description | |----------|---------|-------------| -| `max_open_trades` | 3 | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) -| `stake_currency` | BTC | **Required.** Crypto-currency used for trading. -| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. +| `max_open_trades` | | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) +| `stake_currency` | | **Required.** Crypto-currency used for trading. +| `stake_amount` | | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. | `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals. -| `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). -| `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below. +| `ticker_interval` | 5m | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). +| `fiat_display_currency` | | Fiat currency used to show your profits. More information below. | `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode. | `dry_run_wallet` | 999.9 | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. | `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). @@ -94,8 +94,8 @@ 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` | 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). +| `strategy` | | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`. +| `strategy_path` | | Adds an additional strategy lookup path (must be a directory). | `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. | `internals.heartbeat_interval` | 60 | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages. | `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. From 6ab7f93ce766ac708ea8b29919633dc5a156631d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2019 07:20:40 +0000 Subject: [PATCH 55/76] Bump scipy from 1.3.2 to 1.3.3 Bumps [scipy](https://github.com/scipy/scipy) from 1.3.2 to 1.3.3. - [Release notes](https://github.com/scipy/scipy/releases) - [Commits](https://github.com/scipy/scipy/compare/v1.3.2...v1.3.3) 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 ff8de9cb2..96a22b42e 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,7 +2,7 @@ -r requirements.txt # Required for hyperopt -scipy==1.3.2 +scipy==1.3.3 scikit-learn==0.21.3 scikit-optimize==0.5.2 filelock==3.0.12 From 28f73ecb3d02802f586cc27f15cbaed30769c886 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2019 07:21:16 +0000 Subject: [PATCH 56/76] Bump ccxt from 1.19.54 to 1.19.86 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.19.54 to 1.19.86. - [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.54...1.19.86) 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 2c176e9c3..ec6224c50 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.54 +ccxt==1.19.86 SQLAlchemy==1.3.11 python-telegram-bot==12.2.0 arrow==0.15.4 From 0a7a1290e3ba062deeacba3205141fc763a04cf1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2019 07:21:32 +0000 Subject: [PATCH 57/76] Bump pytest-mock from 1.11.2 to 1.12.1 Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 1.11.2 to 1.12.1. - [Release notes](https://github.com/pytest-dev/pytest-mock/releases) - [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-mock/compare/v1.11.2...v1.12.1) 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 297b95623..34b52c963 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,7 @@ mypy==0.740 pytest==5.2.4 pytest-asyncio==0.10.0 pytest-cov==2.8.1 -pytest-mock==1.11.2 +pytest-mock==1.12.1 pytest-random-order==1.0.4 # Convert jupyter notebooks to markdown documents From 03f02294d1b351b415431cac82deed633b0cda60 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2019 07:21:52 +0000 Subject: [PATCH 58/76] Bump pytest from 5.2.4 to 5.3.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.2.4 to 5.3.0. - [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.4...5.3.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 297b95623..2dc8c88d9 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.4 +pytest==5.3.0 pytest-asyncio==0.10.0 pytest-cov==2.8.1 pytest-mock==1.11.2 From 418ca0030521d5d1d827c12c348820aefd545e83 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2019 07:22:09 +0000 Subject: [PATCH 59/76] Bump jsonschema from 3.1.1 to 3.2.0 Bumps [jsonschema](https://github.com/Julian/jsonschema) from 3.1.1 to 3.2.0. - [Release notes](https://github.com/Julian/jsonschema/releases) - [Changelog](https://github.com/Julian/jsonschema/blob/master/CHANGELOG.rst) - [Commits](https://github.com/Julian/jsonschema/compare/v3.1.1...v3.2.0) 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 2c176e9c3..8f6c7e3d6 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -8,7 +8,7 @@ cachetools==3.1.1 requests==2.22.0 urllib3==1.25.7 wrapt==1.11.2 -jsonschema==3.1.1 +jsonschema==3.2.0 TA-Lib==0.4.17 tabulate==0.8.6 coinmarketcap==5.0.3 From e7c17df844d08656de128d0baf2e83276f5a71e6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 12:56:05 +0100 Subject: [PATCH 60/76] validate defaults in documentation --- docs/configuration.md | 58 +++++++++++++++++++++--------------------- freqtrade/constants.py | 4 +-- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index bf7c07268..68ecd4629 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,67 +40,67 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Command | Default | Description | |----------|---------|-------------| -| `max_open_trades` | | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) +| `max_open_trades` | | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) | `stake_currency` | | **Required.** Crypto-currency used for trading. | `stake_amount` | | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. | `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals. -| `ticker_interval` | 5m | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). +| `ticker_interval` | | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | | Fiat currency used to show your profits. More information below. | `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode. -| `dry_run_wallet` | 999.9 | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. +| `dry_run_wallet` | | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. | `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). -| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). -| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `minimal_roi` | | **Required.** Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). +| `stoploss` | | **Required.** Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `trailing_stop_positive_offset` | 0 | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `unfilledtimeout.buy` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. -| `unfilledtimeout.sell` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. -| `bid_strategy.ask_last_balance` | 0.0 | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). +| `unfilledtimeout.buy` | | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. +| `unfilledtimeout.sell` | | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. +| `bid_strategy.ask_last_balance` | | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). | `bid_strategy.use_order_book` | false | Allows buying of pair using the rates in Order Book Bids. -| `bid_strategy.order_book_top` | 0 | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. +| `bid_strategy.order_book_top` | 1 | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. | `bid_strategy. check_depth_of_market.enabled` | false | Does not buy if the % difference of buy orders and sell orders is met in Order Book. | `bid_strategy. check_depth_of_market.bids_to_ask_delta` | 0 | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. | `ask_strategy.use_order_book` | false | Allows selling of open traded pair using the rates in Order Book Asks. -| `ask_strategy.order_book_min` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. -| `ask_strategy.order_book_max` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. +| `ask_strategy.order_book_min` | 1 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. +| `ask_strategy.order_book_max` | 1 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. | `ask_strategy.use_sell_signal` | true | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). | `ask_strategy.sell_profit_only` | false | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). | `ask_strategy.ignore_roi_if_buy_signal` | false | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). -| `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy). -| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). -| `exchange.name` | | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). +| `order_types` | | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy). +| `order_time_in_force` | | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). +| `exchange.name` | | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | `exchange.sandbox` | false | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. -| `exchange.key` | '' | API key to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.*** -| `exchange.secret` | '' | API secret to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.*** -| `exchange.password` | '' | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. ***Keep it in secrete, do not disclose publicly.*** +| `exchange.key` | | API key to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.*** +| `exchange.secret` | | API secret to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.*** +| `exchange.password` | | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. ***Keep it in secrete, do not disclose publicly.*** | `exchange.pair_whitelist` | [] | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). | `exchange.pair_blacklist` | [] | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). -| `exchange.ccxt_config` | None | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) -| `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) +| `exchange.ccxt_config` | | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) +| `exchange.ccxt_async_config` | | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) | `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. +| `edge.*` | | 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. | `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.*** +| `telegram.enabled` | | Enable the usage of Telegram. +| `telegram.token` | | Your Telegram bot token. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.*** +| `telegram.chat_id` | | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.*** | `webhook.enabled` | false | Enable usage of Webhook notifications -| `webhook.url` | false | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. -| `webhook.webhookbuy` | false | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `webhook.webhooksell` | false | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `webhook.webhookstatus` | false | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. +| `webhook.url` | | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. +| `webhook.webhookbuy` | | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. +| `webhook.webhooksell` | | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. +| `webhook.webhookstatus` | | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. | `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` | | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`. | `strategy_path` | | Adds an additional strategy lookup path (must be a directory). -| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second. +| `internals.process_throttle_secs` | 5 | Set the process throttle. Value in second. | `internals.heartbeat_interval` | 60 | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages. | `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. | `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. -| `user_data_dir` | cwd()/user_data | Directory containing user data. Defaults to `./user_data/`. +| `user_data_dir` | | Directory containing user data. Defaults to `./user_data/`. ### Parameters in the strategy diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b485ba0d8..f6e08bc36 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -93,8 +93,8 @@ CONF_SCHEMA = { 'unfilledtimeout': { 'type': 'object', 'properties': { - 'buy': {'type': 'number', 'minimum': 3}, - 'sell': {'type': 'number', 'minimum': 10} + 'buy': {'type': 'number', 'minimum': 1}, + 'sell': {'type': 'number', 'minimum': 1} } }, 'bid_strategy': { From 37f698d9c1269d5b817a2a52b01d25b4f32450e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 14:20:41 +0100 Subject: [PATCH 61/76] move default values to Description field --- docs/configuration.md | 72 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 68ecd4629..5241eaab9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -43,62 +43,62 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `max_open_trades` | | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) | `stake_currency` | | **Required.** Crypto-currency used for trading. | `stake_amount` | | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. -| `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals. +| `amount_reserve_percent` | | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals. *Defaults to `0.05` (5%).* | `ticker_interval` | | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | | Fiat currency used to show your profits. More information below. -| `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode. +| `dry_run` | | **Required.** Define if the bot must be in Dry-run or production mode. *Defaults to `true`.* | `dry_run_wallet` | | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. -| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). +| `process_only_new_candles` | | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. *Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). | `minimal_roi` | | **Required.** Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). | `stoploss` | | **Required.** Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive_offset` | 0 | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop` | | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive` | | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive_offset` | | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. *Defaults to `0.0` (no offset).* More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_only_offset_is_reached` | | Only apply trailing stoploss when the offset is reached. *Defaults to `false`.* [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `unfilledtimeout.buy` | | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `bid_strategy.ask_last_balance` | | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). -| `bid_strategy.use_order_book` | false | Allows buying of pair using the rates in Order Book Bids. -| `bid_strategy.order_book_top` | 1 | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. -| `bid_strategy. check_depth_of_market.enabled` | false | Does not buy if the % difference of buy orders and sell orders is met in Order Book. -| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | 0 | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. -| `ask_strategy.use_order_book` | false | Allows selling of open traded pair using the rates in Order Book Asks. -| `ask_strategy.order_book_min` | 1 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. -| `ask_strategy.order_book_max` | 1 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. -| `ask_strategy.use_sell_signal` | true | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). -| `ask_strategy.sell_profit_only` | false | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). -| `ask_strategy.ignore_roi_if_buy_signal` | false | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). +| `bid_strategy.use_order_book` | | Enable buying using the rates in Order Book Bids. +| `bid_strategy.order_book_top` | | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. *Defaults to `1`.* +| `bid_strategy. check_depth_of_market.enabled` | | Does not buy if the difference of buy orders and sell orders is met in Order Book. *Defaults to `false`.* +| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. *Defaults to `0`.* +| `ask_strategy.use_order_book` | | Enable selling of open trades using Order Book Asks. +| `ask_strategy.order_book_min` | | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. *Defaults to `1`.* +| `ask_strategy.order_book_max` | | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. *Defaults to `1`.* +| `ask_strategy.use_sell_signal` | | Use sell signals produced by the strategy in addition to the `minimal_roi`. *Defaults to `true`.* [Strategy Override](#parameters-in-the-strategy). +| `ask_strategy.sell_profit_only` | | Wait until the bot makes a positive profit before taking a sell decision. *Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). +| `ask_strategy.ignore_roi_if_buy_signal` | | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. *Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). | `order_types` | | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy). | `order_time_in_force` | | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). | `exchange.name` | | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). -| `exchange.sandbox` | false | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. -| `exchange.key` | | API key to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.*** -| `exchange.secret` | | API secret to use for the exchange. Only required when you are in production mode. ***Keep it in secrete, do not disclose publicly.*** -| `exchange.password` | | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. ***Keep it in secrete, do not disclose publicly.*** -| `exchange.pair_whitelist` | [] | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). -| `exchange.pair_blacklist` | [] | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). +| `exchange.sandbox` | | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. +| `exchange.key` | | API key to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** +| `exchange.secret` | | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** +| `exchange.password` | | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secrete, do not disclose publicly.** +| `exchange.pair_whitelist` | | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). +| `exchange.pair_blacklist` | | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). | `exchange.ccxt_config` | | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) | `exchange.ccxt_async_config` | | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) -| `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded. +| `exchange.markets_refresh_interval` | | The interval in minutes in which markets are reloaded. *Defaults to `60` minutes.* | `edge.*` | | 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. -| `pairlists` | StaticPairList | Define one or more pairlists to be used. [More information below](#dynamic-pairlists). +| `experimental.block_bad_exchanges` | | Block exchanges known to not work with freqtrade. *Defaults to `true`.* Leave on default unless you want to test if that exchange works now. +| `pairlists` | | Define one or more pairlists to be used. *Defaults to `StaticPairList`.* [More information below](#dynamic-pairlists). | `telegram.enabled` | | Enable the usage of Telegram. -| `telegram.token` | | Your Telegram bot token. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.*** -| `telegram.chat_id` | | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. ***Keep it in secrete, do not disclose publicly.*** -| `webhook.enabled` | false | Enable usage of Webhook notifications +| `telegram.token` | | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** +| `telegram.chat_id` | | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** +| `webhook.enabled` | | Enable usage of Webhook notifications | `webhook.url` | | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. | `webhook.webhookbuy` | | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `webhook.webhooksell` | | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. +| `webhook.webhooksell` | | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. | `webhook.webhookstatus` | | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `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. +| `db_url` | | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`, and to `sqlite:///tradesv3.sqlite` for production instances. +| `initial_state` | | Defines the initial application state. More information below. *Defaults to `stopped`.* +| `forcebuy_enable` | | Enables the RPC Commands to force a buy. More information below. | `strategy` | | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`. | `strategy_path` | | Adds an additional strategy lookup path (must be a directory). -| `internals.process_throttle_secs` | 5 | Set the process throttle. Value in second. -| `internals.heartbeat_interval` | 60 | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages. -| `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. +| `internals.process_throttle_secs` | | Set the process throttle. Value in second. *Defaults to `5` seconds.* +| `internals.heartbeat_interval` | | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages. *Defaults to `60` seconds.* +| `internals.sd_notify` | | 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. | `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. | `user_data_dir` | | Directory containing user data. Defaults to `./user_data/`. From 12b9257c6d321e3ef34a3a32acd27b1f7cafb8d3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 14:25:02 +0100 Subject: [PATCH 62/76] new-lines before defaults in documentation --- docs/configuration.md | 126 +++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 5241eaab9..a29621585 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -38,69 +38,69 @@ The prevelance for all Options is as follows: Mandatory parameters are marked as **Required**, which means that they are required to be set in one of the possible ways. -| Command | Default | Description | -|----------|---------|-------------| -| `max_open_trades` | | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) -| `stake_currency` | | **Required.** Crypto-currency used for trading. -| `stake_amount` | | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. -| `amount_reserve_percent` | | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals. *Defaults to `0.05` (5%).* -| `ticker_interval` | | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). -| `fiat_display_currency` | | Fiat currency used to show your profits. More information below. -| `dry_run` | | **Required.** Define if the bot must be in Dry-run or production mode. *Defaults to `true`.* -| `dry_run_wallet` | | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. -| `process_only_new_candles` | | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. *Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). -| `minimal_roi` | | **Required.** Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). -| `stoploss` | | **Required.** Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop` | | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive` | | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive_offset` | | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. *Defaults to `0.0` (no offset).* More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_only_offset_is_reached` | | Only apply trailing stoploss when the offset is reached. *Defaults to `false`.* [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `unfilledtimeout.buy` | | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. -| `unfilledtimeout.sell` | | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. -| `bid_strategy.ask_last_balance` | | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). -| `bid_strategy.use_order_book` | | Enable buying using the rates in Order Book Bids. -| `bid_strategy.order_book_top` | | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. *Defaults to `1`.* -| `bid_strategy. check_depth_of_market.enabled` | | Does not buy if the difference of buy orders and sell orders is met in Order Book. *Defaults to `false`.* -| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. *Defaults to `0`.* -| `ask_strategy.use_order_book` | | Enable selling of open trades using Order Book Asks. -| `ask_strategy.order_book_min` | | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. *Defaults to `1`.* -| `ask_strategy.order_book_max` | | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. *Defaults to `1`.* -| `ask_strategy.use_sell_signal` | | Use sell signals produced by the strategy in addition to the `minimal_roi`. *Defaults to `true`.* [Strategy Override](#parameters-in-the-strategy). -| `ask_strategy.sell_profit_only` | | Wait until the bot makes a positive profit before taking a sell decision. *Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). -| `ask_strategy.ignore_roi_if_buy_signal` | | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. *Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). -| `order_types` | | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy). -| `order_time_in_force` | | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). -| `exchange.name` | | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). -| `exchange.sandbox` | | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. -| `exchange.key` | | API key to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** -| `exchange.secret` | | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** -| `exchange.password` | | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secrete, do not disclose publicly.** -| `exchange.pair_whitelist` | | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). -| `exchange.pair_blacklist` | | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). -| `exchange.ccxt_config` | | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) -| `exchange.ccxt_async_config` | | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) -| `exchange.markets_refresh_interval` | | The interval in minutes in which markets are reloaded. *Defaults to `60` minutes.* -| `edge.*` | | Please refer to [edge configuration document](edge.md) for detailed explanation. -| `experimental.block_bad_exchanges` | | Block exchanges known to not work with freqtrade. *Defaults to `true`.* Leave on default unless you want to test if that exchange works now. -| `pairlists` | | Define one or more pairlists to be used. *Defaults to `StaticPairList`.* [More information below](#dynamic-pairlists). -| `telegram.enabled` | | Enable the usage of Telegram. -| `telegram.token` | | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** -| `telegram.chat_id` | | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** -| `webhook.enabled` | | Enable usage of Webhook notifications -| `webhook.url` | | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. -| `webhook.webhookbuy` | | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `webhook.webhooksell` | | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `webhook.webhookstatus` | | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `db_url` | | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`, and to `sqlite:///tradesv3.sqlite` for production instances. -| `initial_state` | | Defines the initial application state. More information below. *Defaults to `stopped`.* -| `forcebuy_enable` | | Enables the RPC Commands to force a buy. More information below. -| `strategy` | | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`. -| `strategy_path` | | Adds an additional strategy lookup path (must be a directory). -| `internals.process_throttle_secs` | | Set the process throttle. Value in second. *Defaults to `5` seconds.* -| `internals.heartbeat_interval` | | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages. *Defaults to `60` seconds.* -| `internals.sd_notify` | | 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. -| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. -| `user_data_dir` | | Directory containing user data. Defaults to `./user_data/`. +| Command | Description | +|----------|-------------| +| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) +| `stake_currency` | **Required.** Crypto-currency used for trading. +| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. +| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).* +| `ticker_interval` | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). +| `fiat_display_currency` | Fiat currency used to show your profits. More information below. +| `dry_run` | **Required.** Define if the bot must be in Dry-run or production mode.
*Defaults to `true`.* +| `dry_run_wallet` | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. +| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle.
*Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). +| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). +| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop` | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive` | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive.
*Defaults to `0.0` (no offset).* More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* +| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. +| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. +| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). +| `bid_strategy.use_order_book` | Enable buying using the rates in Order Book Bids. +| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. *Defaults to `1`.* +| `bid_strategy. check_depth_of_market.enabled` | Does not buy if the difference of buy orders and sell orders is met in Order Book.
*Defaults to `false`.* +| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. *Defaults to `0`.* +| `ask_strategy.use_order_book` | Enable selling of open trades using Order Book Asks. +| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.* +| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.* +| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.* +| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* +| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* +| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy). +| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). +| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). +| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. +| `exchange.key` | API key to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** +| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** +| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secrete, do not disclose publicly.** +| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). +| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). +| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) +| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) +| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.* +| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. +| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.* +| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
*Defaults to `StaticPairList`.* +| `telegram.enabled` | Enable the usage of Telegram. +| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** +| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** +| `webhook.enabled` | Enable usage of Webhook notifications +| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. +| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. +| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. +| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. +| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`, and to `sqlite:///tradesv3.sqlite` for production instances. +| `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.* +| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. +| `strategy` | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`. +| `strategy_path` | Adds an additional strategy lookup path (must be a directory). +| `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.* +| `internals.heartbeat_interval` | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.* +| `internals.sd_notify` | 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. +| `logfile` | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. +| `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*. ### Parameters in the strategy From 9e7d367b5c30989729848b37b0f5ba8f5eb45208 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Nov 2019 15:43:09 +0100 Subject: [PATCH 63/76] Realign strategy_override paramters --- docs/configuration.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a29621585..296c19d36 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -41,19 +41,19 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Command | Description | |----------|-------------| | `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) -| `stake_currency` | **Required.** Crypto-currency used for trading. -| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. +| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). +| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. [Strategy Override](#parameters-in-the-strategy). | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).* | `ticker_interval` | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | Fiat currency used to show your profits. More information below. | `dry_run` | **Required.** Define if the bot must be in Dry-run or production mode.
*Defaults to `true`.* | `dry_run_wallet` | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. -| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle.
*Defaults to `false`.* [Strategy Override](#parameters-in-the-strategy). +| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* | `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). | `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `trailing_stop` | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `trailing_stop_positive` | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive.
*Defaults to `0.0` (no offset).* More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).* | `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* | `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. @@ -107,15 +107,18 @@ Mandatory parameters are marked as **Required**, which means that they are requi The following parameters can be set in either configuration file or strategy. Values set in the configuration file always overwrite values set in the strategy. -* `ticker_interval` * `minimal_roi` +* `ticker_interval` * `stoploss` * `trailing_stop` * `trailing_stop_positive` * `trailing_stop_positive_offset` +* `trailing_only_offset_is_reached` * `process_only_new_candles` * `order_types` * `order_time_in_force` +* `stake_currency` +* `stake_amount` * `use_sell_signal` (ask_strategy) * `sell_profit_only` (ask_strategy) * `ignore_roi_if_buy_signal` (ask_strategy) From 8204107315fdda493c254b592c86adb448c2b157 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Nov 2019 11:57:02 +0300 Subject: [PATCH 64/76] Add test for get_min_pair_stake_amount() with real data --- tests/test_freqtradebot.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index b01c8e247..937723073 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -334,6 +334,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.stoploss = -0.05 markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} + # no pair found mocker.patch( 'freqtrade.exchange.Exchange.markets', @@ -440,6 +441,25 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: assert result == min(8, 2 * 2) / 0.9 +def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.stoploss = -0.05 + markets = {'ETH/BTC': {'symbol': 'ETH/BTC'}} + + # Real Binance data + markets["ETH/BTC"]["limits"] = { + 'cost': {'min': 0.0001}, + 'amount': {'min': 0.001} + } + mocker.patch( + 'freqtrade.exchange.Exchange.markets', + PropertyMock(return_value=markets) + ) + result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 0.020405) + assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) / 0.9, 8) + def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) From 17269c88bef96618cf91bbe6ceabbefa3ae37c39 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Nov 2019 11:57:58 +0300 Subject: [PATCH 65/76] Fix _get_min_pair_stake_amount() --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 358c63f90..b5d157635 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -266,7 +266,7 @@ class FreqtradeBot: amount_reserve_percent += self.strategy.stoploss # it should not be more than 50% amount_reserve_percent = max(amount_reserve_percent, 0.5) - return min(min_stake_amounts) / amount_reserve_percent + return max(min_stake_amounts) / amount_reserve_percent def create_trades(self) -> bool: """ From 0ac592ad40a473a8a7adfc4ab7319d14c2a00632 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Nov 2019 12:00:20 +0300 Subject: [PATCH 66/76] Fix markets in conftest --- tests/conftest.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fbd23a0dc..bf245a840 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -325,7 +325,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -351,7 +351,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -376,7 +376,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -401,7 +401,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -426,7 +426,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -451,7 +451,7 @@ def get_markets(): }, 'price': 500000, 'cost': { - 'min': 1, + 'min': 0.0001, 'max': 500000, }, }, @@ -479,7 +479,7 @@ def get_markets(): 'max': None }, 'cost': { - 'min': 0.001, + 'min': 0.0001, 'max': None } }, From 8e1e20bf0d536e8b4b46c8ea26757a180419e080 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Nov 2019 12:07:43 +0300 Subject: [PATCH 67/76] Fix some tests --- tests/test_freqtradebot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 937723073..746a05cc6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -299,7 +299,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, limit_buy_order, fee) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - default_conf['stake_amount'] = 0.0000098751 + default_conf['stake_amount'] = 0.00098751 default_conf['max_open_trades'] = 2 mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -313,7 +313,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, trade = Trade.query.first() assert trade is not None - assert trade.stake_amount == 0.0000098751 + assert trade.stake_amount == 0.00098751 assert trade.is_open assert trade.open_date is not None @@ -321,11 +321,11 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker, trade = Trade.query.order_by(Trade.id.desc()).first() assert trade is not None - assert trade.stake_amount == 0.0000098751 + assert trade.stake_amount == 0.00098751 assert trade.is_open assert trade.open_date is not None - assert Trade.total_open_trades_stakes() == 1.97502e-05 + assert Trade.total_open_trades_stakes() == 1.97502e-03 def test_get_min_pair_stake_amount(mocker, default_conf) -> None: From 066f32406058b22b0d3c227745cd205ec6ab3418 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Tue, 26 Nov 2019 12:28:04 +0300 Subject: [PATCH 68/76] Make flake happy --- tests/test_freqtradebot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 746a05cc6..31e9f8750 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -460,6 +460,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 0.020405) assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) / 0.9, 8) + def test_create_trades(default_conf, ticker, limit_buy_order, fee, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) From 585b8332ad61098fe95bce3e674c559363377dc3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2019 11:48:01 +0100 Subject: [PATCH 69/76] Improve tests and unify required attribute --- freqtrade/constants.py | 4 +--- tests/test_configuration.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index f6e08bc36..0d52bf405 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -269,10 +269,8 @@ CONF_SCHEMA = { 'required': ['process_throttle_secs', 'allowed_risk', 'capital_available_percentage'] } }, - 'anyOf': [ - {'required': ['exchange']} - ], 'required': [ + 'exchange', 'max_open_trades', 'stake_currency', 'stake_amount', diff --git a/tests/test_configuration.py b/tests/test_configuration.py index e50ba99ee..60bd6d7df 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -40,10 +40,16 @@ def test_load_config_invalid_pair(default_conf) -> None: def test_load_config_missing_attributes(default_conf) -> None: - default_conf.pop('exchange') + conf = deepcopy(default_conf) + conf.pop('exchange') with pytest.raises(ValidationError, match=r".*'exchange' is a required property.*"): - validate_config_schema(default_conf) + validate_config_schema(conf) + + conf = deepcopy(default_conf) + conf.pop('stake_currency') + with pytest.raises(ValidationError, match=r".*'stake_currency' is a required property.*"): + validate_config_schema(conf) def test_load_config_incorrect_stake_amount(default_conf) -> None: From cceb00c4065c1a66ff7995f7a59304365e75b046 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 26 Nov 2019 12:12:41 +0100 Subject: [PATCH 70/76] Try coveralls --- .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 047a34dd6..f6a111944 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,7 +74,7 @@ jobs: # 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 TRAVIS_BRANCH=${GITHUB_REF#"ref/heads"} - export TRAVIS_BRANCH=${HEAD_REF} + export CI_BRANCH=${GITHUB_REF#"ref/heads"} echo "${TRAVIS_BRANCH}" coveralls || true From f2cd4fdafe989bd6d8713d19126d1dfdf8a646ba Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 27 Nov 2019 05:12:54 +0300 Subject: [PATCH 71/76] Fix the rest of tests --- tests/test_freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 31e9f8750..841bf8a6a 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -426,7 +426,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2) - assert result == min(2, 2 * 2) / 0.9 + assert result == max(2, 2 * 2) / 0.9 # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { @@ -438,7 +438,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2) - assert result == min(8, 2 * 2) / 0.9 + assert result == max(8, 2 * 2) / 0.9 def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: From a373e48939f74c73091d7d010988861c64cf2ee4 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 27 Nov 2019 14:53:01 +0300 Subject: [PATCH 72/76] Comment added --- freqtrade/freqtradebot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index b5d157635..ec341ff0a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -266,6 +266,10 @@ class FreqtradeBot: amount_reserve_percent += self.strategy.stoploss # it should not be more than 50% amount_reserve_percent = max(amount_reserve_percent, 0.5) + + # The value returned should satisfy both limits: for amount (base currency) and + # for cost (quote, stake currency), so max() is used here. + # See also #2575 at github. return max(min_stake_amounts) / amount_reserve_percent def create_trades(self) -> bool: From f0e6a9e0e3973452e18c562cd2188e202210e2ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Nov 2019 14:18:40 +0100 Subject: [PATCH 73/76] Address feedback --- docs/bot-usage.md | 6 +++--- docs/configuration.md | 40 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 4665878d4..25818aea6 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -89,9 +89,9 @@ The bot allows you to use multiple configuration files by specifying multiple defined in the latter configuration files override parameters with the same name defined in the previous configuration files specified in the command line earlier. -For example, you can make a separate configuration file with your key and secrete +For example, you can make a separate configuration file with your key and secret for the Exchange you use for trading, specify default configuration file with -empty key and secrete values while running in the Dry Mode (which does not actually +empty key and secret values while running in the Dry Mode (which does not actually require them): ```bash @@ -104,7 +104,7 @@ and specify both configuration files when running in the normal Live Trade Mode: 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 +This could help you hide your private Exchange key and Exchange secret on you local machine by setting appropriate file permissions for the file which contains actual secrets and, additionally, prevent unintended disclosure of sensitive private data when you publish examples of your configuration in the project issues or in the Internet. diff --git a/docs/configuration.md b/docs/configuration.md index 296c19d36..76bfe8339 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -43,16 +43,16 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) | `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). | `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. [Strategy Override](#parameters-in-the-strategy). -| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).* -| `ticker_interval` | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy). -| `fiat_display_currency` | Fiat currency used to show your profits. More information below. -| `dry_run` | **Required.** Define if the bot must be in Dry-run or production mode.
*Defaults to `true`.* +| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).* +| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). +| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). +| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.* | `dry_run_wallet` | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. | `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* -| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy). -| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop` | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive` | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). +| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). +| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). | `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).* | `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* | `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. @@ -60,7 +60,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). | `bid_strategy.use_order_book` | Enable buying using the rates in Order Book Bids. | `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. *Defaults to `1`.* -| `bid_strategy. check_depth_of_market.enabled` | Does not buy if the difference of buy orders and sell orders is met in Order Book.
*Defaults to `false`.* +| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book.
*Defaults to `false`.* | `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. *Defaults to `0`.* | `ask_strategy.use_order_book` | Enable selling of open trades using Order Book Asks. | `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.* @@ -72,9 +72,9 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. -| `exchange.key` | API key to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** -| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secrete, do not disclose publicly.** -| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secrete, do not disclose publicly.** +| `exchange.key` | API key to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.** +| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.** +| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secret, do not disclose publicly.** | `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). | `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). | `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) @@ -84,22 +84,22 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.* | `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
*Defaults to `StaticPairList`.* | `telegram.enabled` | Enable the usage of Telegram. -| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** -| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secrete, do not disclose publicly.** +| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.** +| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.** | `webhook.enabled` | Enable usage of Webhook notifications | `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. | `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. | `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. | `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`, and to `sqlite:///tradesv3.sqlite` for production instances. +| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. | `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.* -| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. -| `strategy` | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`. +| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. +| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. | `strategy_path` | Adds an additional strategy lookup path (must be a directory). | `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.* -| `internals.heartbeat_interval` | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.* +| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.* | `internals.sd_notify` | 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. -| `logfile` | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file. +| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*. ### Parameters in the strategy @@ -475,7 +475,7 @@ creating trades on the exchange. "db_url": "sqlite:///tradesv3.dryrun.sqlite", ``` -3. Remove your Exchange API key and secrete (change them by empty values or fake credentials): +3. Remove your Exchange API key and secret (change them by empty values or fake credentials): ```json "exchange": { From 64da8771617802d77b1d73e8fa90e7d8cc08a500 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Nov 2019 14:24:14 +0100 Subject: [PATCH 74/76] Update stake_amount description --- docs/configuration.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 76bfe8339..a327ae343 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -42,7 +42,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi |----------|-------------| | `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) | `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). -| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. [Strategy Override](#parameters-in-the-strategy). +| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#understand-stake_amount). [Strategy Override](#parameters-in-the-strategy). | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).* | `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). @@ -126,15 +126,19 @@ Values set in the configuration file always overwrite values set in the strategy ### Understand stake_amount The `stake_amount` configuration parameter is an amount of crypto-currency your bot will use for each trade. -The minimal value is 0.0005. If there is not enough crypto-currency in -the account an exception is generated. + +The minimal configuration value is 0.0001. Please check your exchange's trading minimums to avoid problems. + +This setting works in combination with `max_open_trades`. The maximum capital engaged in trades is `stake_amount * max_open_trades`. +For example, the bot will at most use (0.05 BTC x 3) = 0.15 BTC, assuming a configuration of `max_open_trades=3` and `stake_amount=0.05`. + To allow the bot to trade all the available `stake_currency` in your account set ```json "stake_amount" : "unlimited", ``` -In this case a trade amount is calclulated as: +In this case a trade amount is calculated as: ```python currency_balance / (max_open_trades - current_open_trades) From 111f018c85e98f777f27cad3eb80b3d322155114 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Nov 2019 14:46:09 +0100 Subject: [PATCH 75/76] Add datatype to configuration documentation --- docs/configuration.md | 125 +++++++++++++++++++++-------------------- freqtrade/constants.py | 9 +-- 2 files changed, 70 insertions(+), 64 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a327ae343..2d0764f0c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,67 +40,72 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Command | Description | |----------|-------------| -| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades) -| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). -| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#understand-stake_amount). [Strategy Override](#parameters-in-the-strategy). -| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).* -| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). -| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). -| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.* -| `dry_run_wallet` | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason. -| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* -| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). -| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). -| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).* -| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* -| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. -| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. -| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). -| `bid_strategy.use_order_book` | Enable buying using the rates in Order Book Bids. -| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. *Defaults to `1`.* -| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book.
*Defaults to `false`.* -| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. *Defaults to `0`.* -| `ask_strategy.use_order_book` | Enable selling of open trades using Order Book Asks. -| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.* -| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.* -| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.* -| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* -| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.* -| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy). -| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). -| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). -| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details. -| `exchange.key` | API key to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.** -| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.** -| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secret, do not disclose publicly.** -| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). -| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). -| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) -| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) -| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.* +| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades).
***Datatype:*** *Positive integer (-1 to use `"unlimited"` trades).* +| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* +| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#understand-stake_amount). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Positive number or `"unlimited"`.* +| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
***Datatype:*** *Positive number as ratio.* +| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* +| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
***Datatype:*** *String* +| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
***Datatype:*** *Boolean* +| `dry_run_wallet` | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason.
***Datatype:*** *Float* +| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean* +| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Dict* +| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Float (as ratio)* +| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Boolean* +| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Float* +| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `0.0` (no offset).*
***Datatype:*** *Float* +| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean* +| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
***Datatype:*** *Integer* +| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
***Datatype:*** *Integer* +| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance). +| `bid_strategy.use_order_book` | Enable buying using the rates in Order Book Bids.
***Datatype:*** *Boolean* +| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. *Defaults to `1`.*
***Datatype:*** *Positive Integer* +| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book.
*Defaults to `false`.*
***Datatype:*** *Boolean* +| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. *Defaults to `0`.*
***Datatype:*** *Float (as ratio)* +| `ask_strategy.use_order_book` | Enable selling of open trades using Order Book Asks.
***Datatype:*** *Boolean* +| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
***Datatype:*** *Positive Integer* +| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
*Defaults to `1`.*
***Datatype:*** *Positive Integer* +| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `true`.*
***Datatype:*** *Boolean* +| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean* +| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
***Datatype:*** *Boolean* +| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Dict* +| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Dict* +| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
***Datatype:*** *String* +| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.
***Datatype:*** *Boolean* +| `exchange.key` | API key to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* +| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* +| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* +| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)).
***Datatype:*** *List* +| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)).
***Datatype:*** *List* +| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
***Datatype:*** *Dict* +| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
***Datatype:*** *Dict* +| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
***Datatype:*** *Integer* | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. -| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.* -| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
*Defaults to `StaticPairList`.* -| `telegram.enabled` | Enable the usage of Telegram. -| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.** -| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.** -| `webhook.enabled` | Enable usage of Webhook notifications -| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. -| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details. -| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. -| `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.* -| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. -| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. -| `strategy_path` | Adds an additional strategy lookup path (must be a directory). -| `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.* -| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.* -| `internals.sd_notify` | 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. -| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. -| `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*. +| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
***Datatype:*** *Boolean* +| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
*Defaults to `StaticPairList`.*
***Datatype:*** *List of Dicts* +| `telegram.enabled` | Enable the usage of Telegram.
***Datatype:*** *Boolean* +| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* +| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* +| `webhook.enabled` | Enable usage of Webhook notifications
***Datatype:*** *Boolean* +| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *Boolean* +| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
***Datatype:*** *String* +| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
***Datatype:*** *String* +| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
***Datatype:*** *String* +| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details.
***Datatype:*** *Boolean* +| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details.
***Datatype:*** *IPv4* +| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details.
***Datatype:*** *Integer between 1024 and 65535* +| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* +| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* +| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances.
***Datatype:*** *String, SQLAlchemy connect string* +| `initial_state` | Defines the initial application state. More information below.
*Defaults to `stopped`.*
***Datatype:*** *Enum, either `stopped` or `running`* +| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below.
***Datatype:*** *Boolean* +| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`.
***Datatype:*** *ClassName* +| `strategy_path` | Adds an additional strategy lookup path (must be a directory).
***Datatype:*** *String* +| `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
***Datatype:*** *Integer* +| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.*
***Datatype:*** *Integer* +| `internals.sd_notify` | 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.
***Datatype:*** *Boolean* +| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file.
***Datatype:*** *String* +| `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
***Datatype:*** *String* ### Parameters in the strategy diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 0d52bf405..f5e5969eb 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -106,7 +106,7 @@ CONF_SCHEMA = { 'maximum': 1, 'exclusiveMaximum': False, 'use_order_book': {'type': 'boolean'}, - 'order_book_top': {'type': 'number', 'maximum': 20, 'minimum': 1}, + 'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1}, 'check_depth_of_market': { 'type': 'object', 'properties': { @@ -122,8 +122,8 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'use_order_book': {'type': 'boolean'}, - 'order_book_min': {'type': 'number', 'minimum': 1}, - 'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50}, + 'order_book_min': {'type': 'integer', 'minimum': 1}, + 'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50}, 'use_sell_signal': {'type': 'boolean'}, 'sell_profit_only': {'type': 'boolean'}, 'ignore_roi_if_buy_signal': {'type': 'boolean'} @@ -210,7 +210,7 @@ CONF_SCHEMA = { 'internals': { 'type': 'object', 'properties': { - 'process_throttle_secs': {'type': 'number'}, + 'process_throttle_secs': {'type': 'integer'}, 'interval': {'type': 'integer'}, 'sd_notify': {'type': 'boolean'}, } @@ -278,5 +278,6 @@ CONF_SCHEMA = { 'bid_strategy', 'unfilledtimeout', 'stoploss', + 'minimal_roi', ] } From 997c4262283e1a413fc9139e0e6a71650145224b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Nov 2019 16:51:03 +0100 Subject: [PATCH 76/76] fix some datatypes --- docs/configuration.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 2d0764f0c..024760fb9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,10 +40,10 @@ Mandatory parameters are marked as **Required**, which means that they are requi | Command | Description | |----------|-------------| -| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades).
***Datatype:*** *Positive integer (-1 to use `"unlimited"` trades).* +| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades).
***Datatype:*** *Positive integer or -1.* | `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* -| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#understand-stake_amount). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Positive number or `"unlimited"`.* -| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
***Datatype:*** *Positive number as ratio.* +| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#understand-stake_amount). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *Positive float or `"unlimited"`.* +| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
***Datatype:*** *Positive Float as ratio.* | `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
***Datatype:*** *String* | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
***Datatype:*** *String* | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
***Datatype:*** *Boolean* @@ -79,7 +79,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)).
***Datatype:*** *List* | `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
***Datatype:*** *Dict* | `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
***Datatype:*** *Dict* -| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
***Datatype:*** *Integer* +| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
***Datatype:*** *Positive Integer* | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
***Datatype:*** *Boolean* | `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists).
*Defaults to `StaticPairList`.*
***Datatype:*** *List of Dicts* @@ -87,7 +87,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* | `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. **Keep it in secret, do not disclose publicly.**
***Datatype:*** *String* | `webhook.enabled` | Enable usage of Webhook notifications
***Datatype:*** *Boolean* -| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *Boolean* +| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String* | `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
***Datatype:*** *String* | `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
***Datatype:*** *String* | `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
***Datatype:*** *String* @@ -101,8 +101,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below.
***Datatype:*** *Boolean* | `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`.
***Datatype:*** *ClassName* | `strategy_path` | Adds an additional strategy lookup path (must be a directory).
***Datatype:*** *String* -| `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
***Datatype:*** *Integer* -| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.*
***Datatype:*** *Integer* +| `internals.process_throttle_secs` | Set the process throttle. Value in second.
*Defaults to `5` seconds.*
***Datatype:*** *Positive Integer* +| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages.
*Defaults to `60` seconds.*
***Datatype:*** *Positive Integer or 0* | `internals.sd_notify` | 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.
***Datatype:*** *Boolean* | `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file.
***Datatype:*** *String* | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
***Datatype:*** *String*