From ca9c5edd39331d074f8f0c2eb68fbc47676c400f Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 18:11:21 +0100 Subject: [PATCH 01/10] rename Strategy into StrategyResolver --- freqtrade/analyze.py | 4 +- .../strategy/{strategy.py => resolver.py} | 2 +- freqtrade/tests/optimize/test_hyperopt.py | 12 ++-- freqtrade/tests/strategy/test_strategy.py | 62 +++++++++---------- freqtrade/tests/test_dataframe.py | 6 +- freqtrade/tests/test_misc.py | 2 +- 6 files changed, 44 insertions(+), 44 deletions(-) rename freqtrade/strategy/{strategy.py => resolver.py} (99%) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 8bc552d74..4f0bd415a 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -11,7 +11,7 @@ from pandas import DataFrame, to_datetime from freqtrade.exchange import get_ticker_history from freqtrade.logger import Logger from freqtrade.persistence import Trade -from freqtrade.strategy.strategy import Strategy +from freqtrade.strategy.resolver import StrategyResolver class SignalType(Enum): @@ -35,7 +35,7 @@ class Analyze(object): self.logger = Logger(name=__name__, level=config.get('loglevel')).get_logger() self.config = config - self.strategy = Strategy(self.config) + self.strategy = StrategyResolver(self.config) @staticmethod def parse_ticker_dataframe(ticker: list) -> DataFrame: diff --git a/freqtrade/strategy/strategy.py b/freqtrade/strategy/resolver.py similarity index 99% rename from freqtrade/strategy/strategy.py rename to freqtrade/strategy/resolver.py index d7a89d1de..74391c85d 100644 --- a/freqtrade/strategy/strategy.py +++ b/freqtrade/strategy/resolver.py @@ -17,7 +17,7 @@ from freqtrade.strategy.interface import IStrategy sys.path.insert(0, r'../../user_data/strategies') -class Strategy(object): +class StrategyResolver(object): """ This class contains all the logic to load custom strategy class """ diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 6d376471a..688a7aa8d 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -8,7 +8,7 @@ import pandas as pd from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.optimize.hyperopt import Hyperopt, start -from freqtrade.strategy.strategy import Strategy +from freqtrade.strategy.resolver import StrategyResolver from freqtrade.tests.conftest import default_conf, log_has from freqtrade.tests.optimize.test_backtesting import get_args @@ -62,7 +62,7 @@ def test_start(mocker, default_conf, caplog) -> None: '--epochs', '5' ] args = get_args(args) - Strategy({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'default_strategy'}) start(args) import pprint @@ -80,7 +80,7 @@ def test_loss_calculation_prefer_correct_trade_count() -> None: Test Hyperopt.calculate_loss() """ hyperopt = _HYPEROPT - Strategy({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'default_strategy'}) correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20) over = hyperopt.calculate_loss(1, hyperopt.target_trades + 100, 20) @@ -171,7 +171,7 @@ def test_fmin_best_results(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.logger.Logger.set_format', MagicMock()) - Strategy({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'default_strategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = create_trials(mocker) hyperopt.tickerdata_to_dataframe = MagicMock() @@ -215,7 +215,7 @@ def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None: conf.update({'spaces': 'all'}) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.logger.Logger.set_format', MagicMock()) - Strategy({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'default_strategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = create_trials(mocker) hyperopt.tickerdata_to_dataframe = MagicMock() @@ -258,7 +258,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> No mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.logger.Logger.set_format', MagicMock()) - Strategy({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'default_strategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = trials hyperopt.tickerdata_to_dataframe = MagicMock() diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 7ce9ae0ef..7dc602f58 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -2,49 +2,49 @@ import logging -from freqtrade.strategy.strategy import Strategy +from freqtrade.strategy.resolver import StrategyResolver def test_sanitize_module_name(): - assert Strategy._sanitize_module_name('default_strategy') == 'default_strategy' - assert Strategy._sanitize_module_name('default_strategy.py') == 'default_strategy' - assert Strategy._sanitize_module_name('../default_strategy.py') == 'default_strategy' - assert Strategy._sanitize_module_name('../default_strategy') == 'default_strategy' - assert Strategy._sanitize_module_name('.default_strategy') == '.default_strategy' - assert Strategy._sanitize_module_name('foo-bar') == 'foo-bar' - assert Strategy._sanitize_module_name('foo/bar') == 'bar' + assert StrategyResolver._sanitize_module_name('default_strategy') == 'default_strategy' + assert StrategyResolver._sanitize_module_name('default_strategy.py') == 'default_strategy' + assert StrategyResolver._sanitize_module_name('../default_strategy.py') == 'default_strategy' + assert StrategyResolver._sanitize_module_name('../default_strategy') == 'default_strategy' + assert StrategyResolver._sanitize_module_name('.default_strategy') == '.default_strategy' + assert StrategyResolver._sanitize_module_name('foo-bar') == 'foo-bar' + assert StrategyResolver._sanitize_module_name('foo/bar') == 'bar' def test_search_strategy(): - assert Strategy._search_strategy('default_strategy') == '.' - assert Strategy._search_strategy('test_strategy') == 'user_data.strategies.' - assert Strategy._search_strategy('super_duper') is None + assert StrategyResolver._search_strategy('default_strategy') == '.' + assert StrategyResolver._search_strategy('test_strategy') == 'user_data.strategies.' + assert StrategyResolver._search_strategy('super_duper') is None def test_strategy_structure(): - assert hasattr(Strategy, 'populate_indicators') - assert hasattr(Strategy, 'populate_buy_trend') - assert hasattr(Strategy, 'populate_sell_trend') + assert hasattr(StrategyResolver, 'populate_indicators') + assert hasattr(StrategyResolver, 'populate_buy_trend') + assert hasattr(StrategyResolver, 'populate_sell_trend') def test_load_strategy(result): - strategy = Strategy() + strategy = StrategyResolver() strategy.logger = logging.getLogger(__name__) - assert not hasattr(Strategy, 'custom_strategy') + assert not hasattr(StrategyResolver, 'custom_strategy') strategy._load_strategy('test_strategy') - assert not hasattr(Strategy, 'custom_strategy') + assert not hasattr(StrategyResolver, 'custom_strategy') assert hasattr(strategy.custom_strategy, 'populate_indicators') assert 'adx' in strategy.populate_indicators(result) def test_load_not_found_strategy(caplog): - strategy = Strategy() + strategy = StrategyResolver() strategy.logger = logging.getLogger(__name__) - assert not hasattr(Strategy, 'custom_strategy') + assert not hasattr(StrategyResolver, 'custom_strategy') strategy._load_strategy('NotFoundStrategy') error_msg = "Impossible to load Strategy 'user_data/strategies/{}.py'. This file does not " \ @@ -53,7 +53,7 @@ def test_load_not_found_strategy(caplog): def test_strategy(result): - strategy = Strategy({'strategy': 'default_strategy'}) + strategy = StrategyResolver({'strategy': 'default_strategy'}) assert hasattr(strategy.custom_strategy, 'minimal_roi') assert strategy.minimal_roi[0] == 0.04 @@ -81,11 +81,11 @@ def test_strategy_override_minimal_roi(caplog): "0": 0.5 } } - strategy = Strategy(config) + strategy = StrategyResolver(config) assert hasattr(strategy.custom_strategy, 'minimal_roi') assert strategy.minimal_roi[0] == 0.5 - assert ('freqtrade.strategy.strategy', + assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'minimal_roi\' with value in config file.' ) in caplog.record_tuples @@ -97,11 +97,11 @@ def test_strategy_override_stoploss(caplog): 'strategy': 'default_strategy', 'stoploss': -0.5 } - strategy = Strategy(config) + strategy = StrategyResolver(config) assert hasattr(strategy.custom_strategy, 'stoploss') assert strategy.stoploss == -0.5 - assert ('freqtrade.strategy.strategy', + assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'stoploss\' with value in config file: -0.5.' ) in caplog.record_tuples @@ -114,31 +114,31 @@ def test_strategy_override_ticker_interval(caplog): 'strategy': 'default_strategy', 'ticker_interval': 60 } - strategy = Strategy(config) + strategy = StrategyResolver(config) assert hasattr(strategy.custom_strategy, 'ticker_interval') assert strategy.ticker_interval == 60 - assert ('freqtrade.strategy.strategy', + assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'ticker_interval\' with value in config file: 60.' ) in caplog.record_tuples def test_strategy_fallback_default_strategy(): - strategy = Strategy() + strategy = StrategyResolver() strategy.logger = logging.getLogger(__name__) - assert not hasattr(Strategy, 'custom_strategy') + assert not hasattr(StrategyResolver, 'custom_strategy') strategy._load_strategy('../../super_duper') - assert not hasattr(Strategy, 'custom_strategy') + assert not hasattr(StrategyResolver, 'custom_strategy') def test_strategy_singleton(): - strategy1 = Strategy({'strategy': 'default_strategy'}) + strategy1 = StrategyResolver({'strategy': 'default_strategy'}) assert hasattr(strategy1.custom_strategy, 'minimal_roi') assert strategy1.minimal_roi[0] == 0.04 - strategy2 = Strategy() + strategy2 = StrategyResolver() assert hasattr(strategy2.custom_strategy, 'minimal_roi') assert strategy2.minimal_roi[0] == 0.04 diff --git a/freqtrade/tests/test_dataframe.py b/freqtrade/tests/test_dataframe.py index 1f69a7d32..fc79cb74c 100644 --- a/freqtrade/tests/test_dataframe.py +++ b/freqtrade/tests/test_dataframe.py @@ -4,7 +4,7 @@ import pandas from freqtrade.analyze import Analyze from freqtrade.optimize import load_data -from freqtrade.strategy.strategy import Strategy +from freqtrade.strategy.resolver import StrategyResolver _pairs = ['BTC_ETH'] @@ -21,13 +21,13 @@ def load_dataframe_pair(pairs): def test_dataframe_load(): - Strategy({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'default_strategy'}) dataframe = load_dataframe_pair(_pairs) assert isinstance(dataframe, pandas.core.frame.DataFrame) def test_dataframe_columns_exists(): - Strategy({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'default_strategy'}) dataframe = load_dataframe_pair(_pairs) assert 'high' in dataframe.columns assert 'low' in dataframe.columns diff --git a/freqtrade/tests/test_misc.py b/freqtrade/tests/test_misc.py index 3560b2db1..4d468b589 100644 --- a/freqtrade/tests/test_misc.py +++ b/freqtrade/tests/test_misc.py @@ -47,7 +47,7 @@ def test_common_datearray(default_conf, mocker) -> None: Test common_datearray() :return: None """ - mocker.patch('freqtrade.strategy.strategy.Strategy', MagicMock()) + mocker.patch('freqtrade.strategy.resolver.StrategyResolver', MagicMock()) analyze = Analyze(default_conf) tick = load_tickerdata_file(None, 'BTC_UNITEST', 1) From 6e5c14a95b50871ef61d4e62f0df38d8dbab419f Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 18:14:05 +0100 Subject: [PATCH 02/10] fix mutable default argument --- freqtrade/strategy/resolver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 74391c85d..583f24a3f 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -7,6 +7,7 @@ import importlib import os import sys from collections import OrderedDict +from typing import Optional, Dict from pandas import DataFrame @@ -21,12 +22,14 @@ class StrategyResolver(object): """ This class contains all the logic to load custom strategy class """ - def __init__(self, config: dict = {}) -> None: + def __init__(self, config: Optional[Dict] = None) -> None: """ Load the custom class from config parameter :param config: :return: """ + config = config or {} + self.logger = Logger(name=__name__).get_logger() # Verify the strategy is in the configuration, otherwise fallback to the default strategy From b4d2a3f4950193fa5791a669ef7e6e1f81e57a62 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 20:44:04 +0100 Subject: [PATCH 03/10] refactor StrategyResolver to work with class names --- freqtrade/arguments.py | 2 +- freqtrade/constants.py | 2 +- freqtrade/strategy/default_strategy.py | 2 - freqtrade/strategy/resolver.py | 105 ++++++++---------- freqtrade/tests/optimize/test_backtesting.py | 14 +-- freqtrade/tests/optimize/test_hyperopt.py | 12 +- .../tests/strategy/test_default_strategy.py | 6 +- freqtrade/tests/strategy/test_strategy.py | 30 ++--- freqtrade/tests/test_analyze.py | 2 +- freqtrade/tests/test_configuration.py | 12 +- freqtrade/tests/test_dataframe.py | 6 +- user_data/strategies/test_strategy.py | 4 - 12 files changed, 85 insertions(+), 112 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index c69135117..5396ae682 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -82,7 +82,7 @@ class Arguments(object): '-s', '--strategy', help='specify strategy file (default: %(default)s)', dest='strategy', - default='default_strategy', + default='DefaultStrategy', type=str, metavar='PATH', ) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a3f91d774..61adf307a 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -14,7 +14,7 @@ class Constants(object): TICKER_INTERVAL = 5 # min HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec - DEFAULT_STRATEGY = 'default_strategy' + DEFAULT_STRATEGY = 'DefaultStrategy' # Required json-schema for user specified config CONF_SCHEMA = { diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index ea37735b7..4b645dbd0 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -7,8 +7,6 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.indicator_helpers import fishers_inverse from freqtrade.strategy.interface import IStrategy -class_name = 'DefaultStrategy' - class DefaultStrategy(IStrategy): """ diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 583f24a3f..fd48d1f1c 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -3,11 +3,11 @@ """ This module load custom strategies """ -import importlib +import importlib.util +import inspect import os -import sys from collections import OrderedDict -from typing import Optional, Dict +from typing import Optional, Dict, Type from pandas import DataFrame @@ -15,8 +15,6 @@ from freqtrade.constants import Constants from freqtrade.logger import Logger from freqtrade.strategy.interface import IStrategy -sys.path.insert(0, r'../../user_data/strategies') - class StrategyResolver(object): """ @@ -38,7 +36,7 @@ class StrategyResolver(object): else: strategy = Constants.DEFAULT_STRATEGY - # Load the strategy + # Try to load the strategy self._load_strategy(strategy) # Set attributes @@ -72,26 +70,27 @@ class StrategyResolver(object): def _load_strategy(self, strategy_name: str) -> None: """ - Search and load the custom strategy. If no strategy found, fallback on the default strategy - Set the object into self.custom_strategy + Search and loads the specified strategy. :param strategy_name: name of the module to import :return: None """ - try: - # Start by sanitizing the file name (remove any extensions) - strategy_name = self._sanitize_module_name(filename=strategy_name) - - # Search where can be the strategy file - path = self._search_strategy(filename=strategy_name) - - # Load the strategy - self.custom_strategy = self._load_class(path + strategy_name) + current_path = os.path.dirname(os.path.realpath(__file__)) + abs_paths = [ + os.path.join(current_path, '..', '..', 'user_data', 'strategies'), + current_path, + ] + for path in abs_paths: + self.custom_strategy = self._search_strategy(path, strategy_name) + if self.custom_strategy: + self.logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) + return None + raise ImportError('not found') # Fallback to the default strategy except (ImportError, TypeError) as error: self.logger.error( - "Impossible to load Strategy 'user_data/strategies/%s.py'. This file does not exist" + "Impossible to load Strategy '%s'. This class does not exist" " or contains Python code errors", strategy_name ) @@ -100,50 +99,44 @@ class StrategyResolver(object): error ) - def _load_class(self, filename: str) -> IStrategy: - """ - Import a strategy as a module - :param filename: path to the strategy (path from freqtrade/strategy/) - :return: return the strategy class - """ - module = importlib.import_module(filename, __package__) - custom_strategy = getattr(module, module.class_name) - - self.logger.info("Load strategy class: %s (%s.py)", module.class_name, filename) - return custom_strategy() - @staticmethod - def _sanitize_module_name(filename: str) -> str: + def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]: """ - Remove any extension from filename - :param filename: filename to sanatize - :return: return the filename without extensions + Returns a list of all possible strategies for the given module_path + :param module_path: absolute path to the module + :param strategy_name: Class name of the strategy + :return: Tuple with (name, class) or None """ - filename = os.path.basename(filename) - filename = os.path.splitext(filename)[0] - return filename - @staticmethod - def _search_strategy(filename: str) -> str: - """ - Search for the Strategy file in different folder - 1. search into the user_data/strategies folder - 2. search into the freqtrade/strategy folder - 3. if nothing found, return None - :param strategy_name: module name to search - :return: module path where is the strategy - """ - pwd = os.path.dirname(os.path.realpath(__file__)) + '/' - user_data = os.path.join(pwd, '..', '..', 'user_data', 'strategies', filename + '.py') - strategy_folder = os.path.join(pwd, filename + '.py') + # Generate spec based on absolute path + spec = importlib.util.spec_from_file_location('user_data.strategies', module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) - path = None - if os.path.isfile(user_data): - path = 'user_data.strategies.' - elif os.path.isfile(strategy_folder): - path = '.' + valid_strategies_gen = ( + obj for name, obj in inspect.getmembers(module, inspect.isclass) + if strategy_name == name and IStrategy in obj.__bases__ + ) + return next(valid_strategies_gen, None) - return path + def _search_strategy(self, directory: str, strategy_name: str) -> Optional[IStrategy]: + """ + Search for the strategy_name in the given directory + :param directory: relative or absolute directory path + :return: name of the strategy class + """ + self.logger.debug('Searching for strategy %s in \'%s\'', strategy_name, directory) + for entry in os.listdir(directory): + # Only consider python files + if not entry.endswith('.py'): + self.logger.debug('Ignoring %s', entry) + continue + strategy = StrategyResolver._get_valid_strategies( + os.path.abspath(os.path.join(directory, entry)), strategy_name + ) + if strategy: + return strategy() + return None def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 021474d5c..146da8faa 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -174,7 +174,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> args = [ '--config', 'config.json', - '--strategy', 'default_strategy', + '--strategy', 'DefaultStrategy', 'backtesting' ] @@ -215,7 +215,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non args = [ '--config', 'config.json', - '--strategy', 'default_strategy', + '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', 'backtesting', '--ticker-interval', '1', @@ -277,7 +277,7 @@ def test_start(mocker, default_conf, caplog) -> None: )) args = [ '--config', 'config.json', - '--strategy', 'default_strategy', + '--strategy', 'DefaultStrategy', 'backtesting' ] args = get_args(args) @@ -498,7 +498,7 @@ def test_backtest_ticks(default_conf): def test_backtest_clash_buy_sell(default_conf): - # Override the default buy trend function in our default_strategy + # Override the default buy trend function in our DefaultStrategy def fun(dataframe=None): buy_value = 1 sell_value = 1 @@ -510,7 +510,7 @@ def test_backtest_clash_buy_sell(default_conf): def test_backtest_only_sell(default_conf): - # Override the default buy trend function in our default_strategy + # Override the default buy trend function in our DefaultStrategy def fun(dataframe=None): buy_value = 0 sell_value = 1 @@ -578,12 +578,12 @@ def test_backtest_start_live(default_conf, mocker, caplog): args.live = True args.datadir = None args.export = None - args.strategy = 'default_strategy' + args.strategy = 'DefaultStrategy' args.timerange = '-100' # needed due to MagicMock malleability args = [ '--config', 'config.json', - '--strategy', 'default_strategy', + '--strategy', 'DefaultStrategy', 'backtesting', '--ticker-interval', '1', '--live', diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 688a7aa8d..affb0a6d3 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -57,12 +57,12 @@ def test_start(mocker, default_conf, caplog) -> None: )) args = [ '--config', 'config.json', - '--strategy', 'default_strategy', + '--strategy', 'DefaultStrategy', 'hyperopt', '--epochs', '5' ] args = get_args(args) - StrategyResolver({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'DefaultStrategy'}) start(args) import pprint @@ -80,7 +80,7 @@ def test_loss_calculation_prefer_correct_trade_count() -> None: Test Hyperopt.calculate_loss() """ hyperopt = _HYPEROPT - StrategyResolver({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'DefaultStrategy'}) correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20) over = hyperopt.calculate_loss(1, hyperopt.target_trades + 100, 20) @@ -171,7 +171,7 @@ def test_fmin_best_results(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.logger.Logger.set_format', MagicMock()) - StrategyResolver({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = create_trials(mocker) hyperopt.tickerdata_to_dataframe = MagicMock() @@ -215,7 +215,7 @@ def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None: conf.update({'spaces': 'all'}) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.logger.Logger.set_format', MagicMock()) - StrategyResolver({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = create_trials(mocker) hyperopt.tickerdata_to_dataframe = MagicMock() @@ -258,7 +258,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> No mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.logger.Logger.set_format', MagicMock()) - StrategyResolver({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = trials hyperopt.tickerdata_to_dataframe = MagicMock() diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py index 2b91fbec5..5c9be0b32 100644 --- a/freqtrade/tests/strategy/test_default_strategy.py +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -4,7 +4,7 @@ import pytest from pandas import DataFrame from freqtrade.analyze import Analyze -from freqtrade.strategy.default_strategy import DefaultStrategy, class_name +from freqtrade.strategy.default_strategy import DefaultStrategy @pytest.fixture @@ -13,10 +13,6 @@ def result(): return Analyze.parse_ticker_dataframe(json.load(data_file)) -def test_default_strategy_class_name(): - assert class_name == DefaultStrategy.__name__ - - def test_default_strategy_structure(): assert hasattr(DefaultStrategy, 'minimal_roi') assert hasattr(DefaultStrategy, 'stoploss') diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 7dc602f58..a34aba4b5 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -5,20 +5,10 @@ import logging from freqtrade.strategy.resolver import StrategyResolver -def test_sanitize_module_name(): - assert StrategyResolver._sanitize_module_name('default_strategy') == 'default_strategy' - assert StrategyResolver._sanitize_module_name('default_strategy.py') == 'default_strategy' - assert StrategyResolver._sanitize_module_name('../default_strategy.py') == 'default_strategy' - assert StrategyResolver._sanitize_module_name('../default_strategy') == 'default_strategy' - assert StrategyResolver._sanitize_module_name('.default_strategy') == '.default_strategy' - assert StrategyResolver._sanitize_module_name('foo-bar') == 'foo-bar' - assert StrategyResolver._sanitize_module_name('foo/bar') == 'bar' - - def test_search_strategy(): - assert StrategyResolver._search_strategy('default_strategy') == '.' - assert StrategyResolver._search_strategy('test_strategy') == 'user_data.strategies.' - assert StrategyResolver._search_strategy('super_duper') is None + assert StrategyResolver._search_strategy('DefaultStrategy') == '.' + assert StrategyResolver._search_strategy('TestStrategy') == 'user_data.strategies.' + assert StrategyResolver._search_strategy('NotFoundStrategy') is None def test_strategy_structure(): @@ -32,7 +22,7 @@ def test_load_strategy(result): strategy.logger = logging.getLogger(__name__) assert not hasattr(StrategyResolver, 'custom_strategy') - strategy._load_strategy('test_strategy') + strategy._load_strategy('TestStrategy') assert not hasattr(StrategyResolver, 'custom_strategy') @@ -47,13 +37,13 @@ def test_load_not_found_strategy(caplog): assert not hasattr(StrategyResolver, 'custom_strategy') strategy._load_strategy('NotFoundStrategy') - error_msg = "Impossible to load Strategy 'user_data/strategies/{}.py'. This file does not " \ + error_msg = "Impossible to load Strategy '{}'. This class does not " \ "exist or contains Python code errors".format('NotFoundStrategy') assert ('test_strategy', logging.ERROR, error_msg) in caplog.record_tuples def test_strategy(result): - strategy = StrategyResolver({'strategy': 'default_strategy'}) + strategy = StrategyResolver({'strategy': 'DefaultStrategy'}) assert hasattr(strategy.custom_strategy, 'minimal_roi') assert strategy.minimal_roi[0] == 0.04 @@ -76,7 +66,7 @@ def test_strategy(result): def test_strategy_override_minimal_roi(caplog): caplog.set_level(logging.INFO) config = { - 'strategy': 'default_strategy', + 'strategy': 'DefaultStrategy', 'minimal_roi': { "0": 0.5 } @@ -94,7 +84,7 @@ def test_strategy_override_minimal_roi(caplog): def test_strategy_override_stoploss(caplog): caplog.set_level(logging.INFO) config = { - 'strategy': 'default_strategy', + 'strategy': 'DefaultStrategy', 'stoploss': -0.5 } strategy = StrategyResolver(config) @@ -111,7 +101,7 @@ def test_strategy_override_ticker_interval(caplog): caplog.set_level(logging.INFO) config = { - 'strategy': 'default_strategy', + 'strategy': 'DefaultStrategy', 'ticker_interval': 60 } strategy = StrategyResolver(config) @@ -134,7 +124,7 @@ def test_strategy_fallback_default_strategy(): def test_strategy_singleton(): - strategy1 = StrategyResolver({'strategy': 'default_strategy'}) + strategy1 = StrategyResolver({'strategy': 'DefaultStrategy'}) assert hasattr(strategy1.custom_strategy, 'minimal_roi') assert strategy1.minimal_roi[0] == 0.04 diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index 558ea7ee5..a4f1ba549 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -16,7 +16,7 @@ from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.tests.conftest import log_has # Avoid to reinit the same object again and again -_ANALYZE = Analyze({'strategy': 'default_strategy'}) +_ANALYZE = Analyze({'strategy': 'DefaultStrategy'}) def test_signaltype_object() -> None: diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 002eac722..1085b0060 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -99,7 +99,7 @@ def test_load_config(default_conf, mocker) -> None: validated_conf = configuration.load_config() assert 'strategy' in validated_conf - assert validated_conf['strategy'] == 'default_strategy' + assert validated_conf['strategy'] == 'DefaultStrategy' assert 'dynamic_whitelist' not in validated_conf assert 'dry_run_db' not in validated_conf @@ -114,7 +114,7 @@ def test_load_config_with_params(default_conf, mocker) -> None: args = [ '--dynamic-whitelist', '10', - '--strategy', 'test_strategy', + '--strategy', 'TestStrategy', '--dry-run-db' ] args = Arguments(args, '').get_parsed_arg() @@ -125,7 +125,7 @@ def test_load_config_with_params(default_conf, mocker) -> None: assert 'dynamic_whitelist' in validated_conf assert validated_conf['dynamic_whitelist'] == 10 assert 'strategy' in validated_conf - assert validated_conf['strategy'] == 'test_strategy' + assert validated_conf['strategy'] == 'TestStrategy' assert 'dry_run_db' in validated_conf assert validated_conf['dry_run_db'] is True @@ -140,7 +140,7 @@ def test_show_info(default_conf, mocker, caplog) -> None: args = [ '--dynamic-whitelist', '10', - '--strategy', 'test_strategy', + '--strategy', 'TestStrategy', '--dry-run-db' ] args = Arguments(args, '').get_parsed_arg() @@ -184,7 +184,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> args = [ '--config', 'config.json', - '--strategy', 'default_strategy', + '--strategy', 'DefaultStrategy', 'backtesting' ] @@ -228,7 +228,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non args = [ '--config', 'config.json', - '--strategy', 'default_strategy', + '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', 'backtesting', '--ticker-interval', '1', diff --git a/freqtrade/tests/test_dataframe.py b/freqtrade/tests/test_dataframe.py index fc79cb74c..b739ae370 100644 --- a/freqtrade/tests/test_dataframe.py +++ b/freqtrade/tests/test_dataframe.py @@ -15,19 +15,19 @@ def load_dataframe_pair(pairs): assert isinstance(pairs[0], str) dataframe = ld[pairs[0]] - analyze = Analyze({'strategy': 'default_strategy'}) + analyze = Analyze({'strategy': 'DefaultStrategy'}) dataframe = analyze.analyze_ticker(dataframe) return dataframe def test_dataframe_load(): - StrategyResolver({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'DefaultStrategy'}) dataframe = load_dataframe_pair(_pairs) assert isinstance(dataframe, pandas.core.frame.DataFrame) def test_dataframe_columns_exists(): - StrategyResolver({'strategy': 'default_strategy'}) + StrategyResolver({'strategy': 'DefaultStrategy'}) dataframe = load_dataframe_pair(_pairs) assert 'high' in dataframe.columns assert 'low' in dataframe.columns diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index a164812c4..4ba1dbe17 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -10,10 +10,6 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib import numpy # noqa -# Update this variable if you change the class name -class_name = 'TestStrategy' - - # This class is a sample. Feel free to customize it. class TestStrategy(IStrategy): """ From a38c2121cc8dce2c97dbde31369bed83fc5a9d5a Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 21:56:20 +0100 Subject: [PATCH 04/10] adapt tests --- freqtrade/strategy/resolver.py | 24 ++++++++++++----------- freqtrade/tests/strategy/test_strategy.py | 17 +++++++--------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index fd48d1f1c..2e1136fde 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -16,6 +16,9 @@ from freqtrade.logger import Logger from freqtrade.strategy.interface import IStrategy +logger = Logger(name=__name__).get_logger() + + class StrategyResolver(object): """ This class contains all the logic to load custom strategy class @@ -28,8 +31,6 @@ class StrategyResolver(object): """ config = config or {} - self.logger = Logger(name=__name__).get_logger() - # Verify the strategy is in the configuration, otherwise fallback to the default strategy if 'strategy' in config: strategy = config['strategy'] @@ -43,17 +44,17 @@ class StrategyResolver(object): # Check if we need to override configuration if 'minimal_roi' in config: self.custom_strategy.minimal_roi = config['minimal_roi'] - self.logger.info("Override strategy \'minimal_roi\' with value in config file.") + logger.info("Override strategy \'minimal_roi\' with value in config file.") if 'stoploss' in config: self.custom_strategy.stoploss = config['stoploss'] - self.logger.info( + logger.info( "Override strategy \'stoploss\' with value in config file: %s.", config['stoploss'] ) if 'ticker_interval' in config: self.custom_strategy.ticker_interval = config['ticker_interval'] - self.logger.info( + logger.info( "Override strategy \'ticker_interval\' with value in config file: %s.", config['ticker_interval'] ) @@ -83,18 +84,18 @@ class StrategyResolver(object): for path in abs_paths: self.custom_strategy = self._search_strategy(path, strategy_name) if self.custom_strategy: - self.logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) + logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) return None raise ImportError('not found') # Fallback to the default strategy except (ImportError, TypeError) as error: - self.logger.error( + logger.error( "Impossible to load Strategy '%s'. This class does not exist" " or contains Python code errors", strategy_name ) - self.logger.error( + logger.error( "The error is:\n%s.", error ) @@ -119,17 +120,18 @@ class StrategyResolver(object): ) return next(valid_strategies_gen, None) - def _search_strategy(self, directory: str, strategy_name: str) -> Optional[IStrategy]: + @staticmethod + def _search_strategy(directory: str, strategy_name: str) -> Optional[IStrategy]: """ Search for the strategy_name in the given directory :param directory: relative or absolute directory path :return: name of the strategy class """ - self.logger.debug('Searching for strategy %s in \'%s\'', strategy_name, directory) + logger.debug('Searching for strategy %s in \'%s\'', strategy_name, directory) for entry in os.listdir(directory): # Only consider python files if not entry.endswith('.py'): - self.logger.debug('Ignoring %s', entry) + logger.debug('Ignoring %s', entry) continue strategy = StrategyResolver._get_valid_strategies( os.path.abspath(os.path.join(directory, entry)), strategy_name diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index a34aba4b5..b6117fd00 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -2,19 +2,16 @@ import logging +import os + +from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.resolver import StrategyResolver def test_search_strategy(): - assert StrategyResolver._search_strategy('DefaultStrategy') == '.' - assert StrategyResolver._search_strategy('TestStrategy') == 'user_data.strategies.' - assert StrategyResolver._search_strategy('NotFoundStrategy') is None - - -def test_strategy_structure(): - assert hasattr(StrategyResolver, 'populate_indicators') - assert hasattr(StrategyResolver, 'populate_buy_trend') - assert hasattr(StrategyResolver, 'populate_sell_trend') + default_location = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', 'strategy') + assert isinstance(StrategyResolver._search_strategy(default_location, 'DefaultStrategy'), IStrategy) + assert StrategyResolver._search_strategy(default_location, 'NotFoundStrategy') is None def test_load_strategy(result): @@ -39,7 +36,7 @@ def test_load_not_found_strategy(caplog): error_msg = "Impossible to load Strategy '{}'. This class does not " \ "exist or contains Python code errors".format('NotFoundStrategy') - assert ('test_strategy', logging.ERROR, error_msg) in caplog.record_tuples + assert ('freqtrade.strategy.resolver', logging.ERROR, error_msg) in caplog.record_tuples def test_strategy(result): From 3cee94226f2eb128fa65489b9a9202eaf2cd7698 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 22:16:42 +0100 Subject: [PATCH 05/10] fix flake8 warnings --- freqtrade/tests/strategy/test_strategy.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index b6117fd00..3898a9e38 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -9,8 +9,12 @@ from freqtrade.strategy.resolver import StrategyResolver def test_search_strategy(): - default_location = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', 'strategy') - assert isinstance(StrategyResolver._search_strategy(default_location, 'DefaultStrategy'), IStrategy) + default_location = os.path.join(os.path.dirname( + os.path.realpath(__file__)), '..', '..', 'strategy' + ) + assert isinstance( + StrategyResolver._search_strategy(default_location, 'DefaultStrategy'), IStrategy + ) assert StrategyResolver._search_strategy(default_location, 'NotFoundStrategy') is None From 4fac61387fb215341d06deda66a59a3bd6546d5a Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 22:28:42 +0100 Subject: [PATCH 06/10] adapt docs/bot-optimization --- docs/bot-optimization.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 6911e9e20..7e64cd95a 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -14,12 +14,12 @@ Since the version `0.16.0` the bot allows using custom strategy file. This is very simple. Copy paste your strategy file into the folder `user_data/strategies`. -Let assume you have a strategy file `awesome-strategy.py`: +Let assume you have a class called `AwesomeStragety` in the file `awesome-strategy.py`: 1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py` -2. Start the bot with the param `--strategy awesome-strategy` (the parameter is the name of the file without '.py') +2. Start the bot with the param `--strategy AwesomeStragety` (the parameter is the class name) ```bash -python3 ./freqtrade/main.py --strategy awesome_strategy +python3 ./freqtrade/main.py --strategy AwesomeStragety ``` ## Change your strategy @@ -35,11 +35,11 @@ A strategy file contains all the information needed to build a good strategy: - Stoploss recommended - Hyperopt parameter -The bot also include a sample strategy you can update: `user_data/strategies/test_strategy.py`. -You can test it with the parameter: `--strategy test_strategy` +The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`. +You can test it with the parameter: `--strategy TestStrategy` ```bash -python3 ./freqtrade/main.py --strategy awesome_strategy +python3 ./freqtrade/main.py --strategy AwesomeStrategy ``` **For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py) From bd2a6467fe9fe01a7c0ee26cd18bc14d76d7b27d Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 22:30:21 +0100 Subject: [PATCH 07/10] adapt argument description and metavar --- freqtrade/arguments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 5396ae682..052b4534e 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -80,11 +80,11 @@ class Arguments(object): ) self.parser.add_argument( '-s', '--strategy', - help='specify strategy file (default: %(default)s)', + help='specify strategy class name (default: %(default)s)', dest='strategy', default='DefaultStrategy', type=str, - metavar='PATH', + metavar='NAME', ) self.parser.add_argument( '--dynamic-whitelist', From 6b47c39103657ac804745c87331c6dcf07e6d28d Mon Sep 17 00:00:00 2001 From: gcarq Date: Sat, 24 Mar 2018 22:51:04 +0100 Subject: [PATCH 08/10] remove invalid mock --- freqtrade/tests/test_misc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/tests/test_misc.py b/freqtrade/tests/test_misc.py index 4d468b589..837a2a66f 100644 --- a/freqtrade/tests/test_misc.py +++ b/freqtrade/tests/test_misc.py @@ -47,8 +47,6 @@ def test_common_datearray(default_conf, mocker) -> None: Test common_datearray() :return: None """ - mocker.patch('freqtrade.strategy.resolver.StrategyResolver', MagicMock()) - analyze = Analyze(default_conf) tick = load_tickerdata_file(None, 'BTC_UNITEST', 1) tickerlist = {'BTC_UNITEST': tick} From 7fe0ec540770b46237b4c88187fceee78a263df2 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sun, 25 Mar 2018 16:39:31 +0200 Subject: [PATCH 09/10] adapt docs/bot-usage to reflect changes --- docs/bot-usage.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/bot-usage.md b/docs/bot-usage.md index cf3258465..b00377982 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -26,9 +26,8 @@ optional arguments: --version show program's version number and exit -c PATH, --config PATH specify configuration file (default: config.json) - -s PATH, --strategy PATH - specify strategy file (default: - freqtrade/strategy/default_strategy.py) + -s NAME, --strategy NAME + specify strategy class name (default: DefaultStrategy) --dry-run-db Force dry run to use a local DB "tradesv3.dry_run.sqlite" instead of memory DB. Work only if dry_run is enabled. @@ -48,21 +47,19 @@ python3 ./freqtrade/main.py -c path/far/far/away/config.json ``` ### How to use --strategy? -This parameter will allow you to load your custom strategy file. Per -default without `--strategy` or `-s` the bot will load the -`default_strategy` included with the bot (`freqtrade/strategy/default_strategy.py`). +This parameter will allow you to load your custom strategy class. +Per default without `--strategy` or `-s` the bot will load the +`DefaultStrategy` included with the bot (`freqtrade/strategy/default_strategy.py`). -The bot will search your strategy file into `user_data/strategies` and -`freqtrade/strategy`. +The bot will search your strategy file within `user_data/strategies` and `freqtrade/strategy`. -To load a strategy, simply pass the file name (without .py) in this -parameters. +To load a strategy, simply pass the class name (e.g.: `CustomStrategy`) in this parameter. **Example:** -In `user_data/strategies` you have a file `my_awesome_strategy.py` to -load it: +In `user_data/strategies` you have a file `my_awesome_strategy.py` which has +a strategy class called `AwesomeStrategy` to load it: ```bash -python3 ./freqtrade/main.py --strategy my_awesome_strategy +python3 ./freqtrade/main.py --strategy AwesomeStrategy ``` If the bot does not find your strategy file, it will display in an error From 7edbae893d0da0ba3b8c377426c954e131a1f263 Mon Sep 17 00:00:00 2001 From: gcarq Date: Sun, 25 Mar 2018 16:42:20 +0200 Subject: [PATCH 10/10] docs: fix typos --- docs/bot-optimization.md | 6 +++--- docs/bot-usage.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 7e64cd95a..00938adbe 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -14,12 +14,12 @@ Since the version `0.16.0` the bot allows using custom strategy file. This is very simple. Copy paste your strategy file into the folder `user_data/strategies`. -Let assume you have a class called `AwesomeStragety` in the file `awesome-strategy.py`: +Let assume you have a class called `AwesomeStrategy` in the file `awesome-strategy.py`: 1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py` -2. Start the bot with the param `--strategy AwesomeStragety` (the parameter is the class name) +2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name) ```bash -python3 ./freqtrade/main.py --strategy AwesomeStragety +python3 ./freqtrade/main.py --strategy AwesomeStrategy ``` ## Change your strategy diff --git a/docs/bot-usage.md b/docs/bot-usage.md index b00377982..ea9b1e4d8 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -57,7 +57,7 @@ To load a strategy, simply pass the class name (e.g.: `CustomStrategy`) in this **Example:** In `user_data/strategies` you have a file `my_awesome_strategy.py` which has -a strategy class called `AwesomeStrategy` to load it: +a strategy class called `AwesomeStrategy` to load it: ```bash python3 ./freqtrade/main.py --strategy AwesomeStrategy ```