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 '