From 4599c80e79aeafbdf4ec0508a7dee2f3951af23e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Jan 2019 07:10:25 +0100 Subject: [PATCH 1/7] Add trailing-stop to strategy --- freqtrade/resolvers/strategy_resolver.py | 27 ++++++++++++++++ freqtrade/strategy/interface.py | 5 +++ freqtrade/tests/strategy/test_strategy.py | 39 +++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index c1d967383..ee00b419b 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -56,6 +56,33 @@ class StrategyResolver(IResolver): else: config['stoploss'] = self.strategy.stoploss + if 'trailing_stop' in config: + self.strategy.trailing_stop = config['trailing_stop'] + logger.info( + "Override strategy 'trailing_stop' with value in config file: %s.", + config['trailing_stop'] + ) + elif hasattr(self.strategy, "trailing_stop"): + config['trailing_stop'] = self.strategy.trailing_stop + + if 'trailing_stop_positive' in config: + self.strategy.trailing_stop_positive = config['trailing_stop_positive'] + logger.info( + "Override strategy 'trailing_stop_positive' with value in config file: %s.", + config['trailing_stop_positive'] + ) + elif hasattr(self.strategy, "trailing_stop_positive"): + config['trailing_stop_positive'] = self.strategy.trailing_stop_positive + + if 'trailing_stop_positive_offset' in config: + self.strategy.trailing_stop_positive_offset = config['trailing_stop_positive_offset'] + logger.info( + "Override strategy 'trailing_stop_positive_offset' with value in config file: %s.", + config['trailing_stop_positive_offset'] + ) + elif hasattr(self.strategy, "trailing_stop_positive_offset"): + config['trailing_stop_positive_offset'] = self.strategy.trailing_stop_positive_offset + if 'ticker_interval' in config: self.strategy.ticker_interval = config['ticker_interval'] logger.info( diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 694430fd6..a6569ec19 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -67,6 +67,11 @@ class IStrategy(ABC): # associated stoploss stoploss: float + # trailing stoploss + trailing_stop: bool = False + trailing_stop_positive: float + trailing_stop_positive_offset: float + # associated ticker interval ticker_interval: str diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 08d95fc5d..676ffc95b 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -150,6 +150,45 @@ def test_strategy_override_stoploss(caplog): ) in caplog.record_tuples +def test_strategy_override_trailing_stop(caplog): + caplog.set_level(logging.INFO) + config = { + 'strategy': 'DefaultStrategy', + 'trailing_stop': True + } + resolver = StrategyResolver(config) + + assert resolver.strategy.trailing_stop + assert isinstance(resolver.strategy.trailing_stop, bool) + assert ('freqtrade.resolvers.strategy_resolver', + logging.INFO, + "Override strategy 'trailing_stop' with value in config file: True." + ) in caplog.record_tuples + + +def test_strategy_override_trailing_stop_positive(caplog): + caplog.set_level(logging.INFO) + config = { + 'strategy': 'DefaultStrategy', + 'trailing_stop_positive': -0.1, + 'trailing_stop_positive_offset': -0.2 + + } + resolver = StrategyResolver(config) + + assert resolver.strategy.trailing_stop_positive == -0.1 + assert ('freqtrade.resolvers.strategy_resolver', + logging.INFO, + "Override strategy 'trailing_stop_positive' with value in config file: -0.1." + ) in caplog.record_tuples + + assert resolver.strategy.trailing_stop_positive_offset == -0.2 + assert ('freqtrade.resolvers.strategy_resolver', + logging.INFO, + "Override strategy 'trailing_stop_positive' with value in config file: -0.1." + ) in caplog.record_tuples + + def test_strategy_override_ticker_interval(caplog): caplog.set_level(logging.INFO) From 5e234420325469d145f3c79e2d078eac5f1a1c08 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Jan 2019 07:20:38 +0100 Subject: [PATCH 2/7] Simplify StrategyResolver by code deduplication --- freqtrade/resolvers/strategy_resolver.py | 104 +++++----------------- freqtrade/tests/strategy/test_strategy.py | 3 +- 2 files changed, 24 insertions(+), 83 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index ee00b419b..3be73f982 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -38,87 +38,35 @@ class StrategyResolver(IResolver): self.strategy: IStrategy = self._load_strategy(strategy_name, config=config, extra_dir=config.get('strategy_path')) - # Set attributes # Check if we need to override configuration - if 'minimal_roi' in config: - self.strategy.minimal_roi = config['minimal_roi'] - logger.info("Override strategy 'minimal_roi' with value in config file: %s.", - config['minimal_roi']) - else: - config['minimal_roi'] = self.strategy.minimal_roi + self._override_attribute_helper(config, "minimal_roi") + self._override_attribute_helper(config, "ticker_interval") + self._override_attribute_helper(config, "stoploss") + self._override_attribute_helper(config, "trailing_stop") + self._override_attribute_helper(config, "trailing_stop_positive") + self._override_attribute_helper(config, "trailing_stop_positive_offset") + self._override_attribute_helper(config, "process_only_new_candles") + self._override_attribute_helper(config, "order_types") + self._override_attribute_helper(config, "order_time_in_force") - if 'stoploss' in config: - self.strategy.stoploss = config['stoploss'] - logger.info( - "Override strategy 'stoploss' with value in config file: %s.", config['stoploss'] - ) - else: - config['stoploss'] = self.strategy.stoploss + # Sort and apply type conversions + self.strategy.minimal_roi = OrderedDict(sorted( + {int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(), + key=lambda t: t[0])) + self.strategy.stoploss = float(self.strategy.stoploss) - if 'trailing_stop' in config: - self.strategy.trailing_stop = config['trailing_stop'] - logger.info( - "Override strategy 'trailing_stop' with value in config file: %s.", - config['trailing_stop'] - ) - elif hasattr(self.strategy, "trailing_stop"): - config['trailing_stop'] = self.strategy.trailing_stop + self._strategy_sanity_validations() - if 'trailing_stop_positive' in config: - self.strategy.trailing_stop_positive = config['trailing_stop_positive'] - logger.info( - "Override strategy 'trailing_stop_positive' with value in config file: %s.", - config['trailing_stop_positive'] - ) - elif hasattr(self.strategy, "trailing_stop_positive"): - config['trailing_stop_positive'] = self.strategy.trailing_stop_positive - - if 'trailing_stop_positive_offset' in config: - self.strategy.trailing_stop_positive_offset = config['trailing_stop_positive_offset'] - logger.info( - "Override strategy 'trailing_stop_positive_offset' with value in config file: %s.", - config['trailing_stop_positive_offset'] - ) - elif hasattr(self.strategy, "trailing_stop_positive_offset"): - config['trailing_stop_positive_offset'] = self.strategy.trailing_stop_positive_offset - - if 'ticker_interval' in config: - self.strategy.ticker_interval = config['ticker_interval'] - logger.info( - "Override strategy 'ticker_interval' with value in config file: %s.", - config['ticker_interval'] - ) - else: - config['ticker_interval'] = self.strategy.ticker_interval - - if 'process_only_new_candles' in config: - self.strategy.process_only_new_candles = config['process_only_new_candles'] - logger.info( - "Override process_only_new_candles 'process_only_new_candles' " - "with value in config file: %s.", config['process_only_new_candles'] - ) - else: - config['process_only_new_candles'] = self.strategy.process_only_new_candles - - if 'order_types' in config: - self.strategy.order_types = config['order_types'] - logger.info( - "Override strategy 'order_types' with value in config file: %s.", - config['order_types'] - ) - else: - config['order_types'] = self.strategy.order_types - - if 'order_time_in_force' in config: - self.strategy.order_time_in_force = config['order_time_in_force'] - logger.info( - "Override strategy 'order_time_in_force' with value in config file: %s.", - config['order_time_in_force'] - ) - else: - config['order_time_in_force'] = self.strategy.order_time_in_force + def _override_attribute_helper(self, config, attribute: str): + if attribute in config: + setattr(self.strategy, attribute, config[attribute]) + logger.info("Override strategy '%s' with value in config file: %s.", + attribute, config[attribute]) + elif hasattr(self.strategy, attribute): + config[attribute] = getattr(self.strategy, attribute) + def _strategy_sanity_validations(self): if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES): raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. " f"Order-types mapping is incomplete.") @@ -127,12 +75,6 @@ class StrategyResolver(IResolver): raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. " f"Order-time-in-force mapping is incomplete.") - # Sort and apply type conversions - self.strategy.minimal_roi = OrderedDict(sorted( - {int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(), - key=lambda t: t[0])) - self.strategy.stoploss = float(self.strategy.stoploss) - def _load_strategy( self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy: """ diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 676ffc95b..b2315f381 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -217,8 +217,7 @@ def test_strategy_override_process_only_new_candles(caplog): assert resolver.strategy.process_only_new_candles assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, - "Override process_only_new_candles 'process_only_new_candles' " - "with value in config file: True." + "Override strategy 'process_only_new_candles' with value in config file: True." ) in caplog.record_tuples From a7dc6b18aaeb27f0460af9555c277e83ac5c4be0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Jan 2019 07:22:19 +0100 Subject: [PATCH 3/7] Overridable attributs as list --- freqtrade/resolvers/strategy_resolver.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 3be73f982..d3972d0ba 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -40,15 +40,19 @@ class StrategyResolver(IResolver): extra_dir=config.get('strategy_path')) # Set attributes # Check if we need to override configuration - self._override_attribute_helper(config, "minimal_roi") - self._override_attribute_helper(config, "ticker_interval") - self._override_attribute_helper(config, "stoploss") - self._override_attribute_helper(config, "trailing_stop") - self._override_attribute_helper(config, "trailing_stop_positive") - self._override_attribute_helper(config, "trailing_stop_positive_offset") - self._override_attribute_helper(config, "process_only_new_candles") - self._override_attribute_helper(config, "order_types") - self._override_attribute_helper(config, "order_time_in_force") + attributes = ["minimal_roi", + "ticker_interval", + "stoploss", + "trailing_stop", + "trailing_stop_positive", + "trailing_stop_positive_offset", + "process_only_new_candles", + "order_types", + "order_time_in_force" + ] + for attribute in attributes: + self._override_attribute_helper(config, attribute) + # Sort and apply type conversions self.strategy.minimal_roi = OrderedDict(sorted( From 00c5ac56d438162a704a2e32d708014445b825a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Jan 2019 07:24:15 +0100 Subject: [PATCH 4/7] Print startup strategy summary --- freqtrade/resolvers/strategy_resolver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index d3972d0ba..35c02599a 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -52,6 +52,9 @@ class StrategyResolver(IResolver): ] for attribute in attributes: self._override_attribute_helper(config, attribute) + if attribute in config: + logger.info("Strategy using %s: %s", attribute, config[attribute]) + # Sort and apply type conversions From cacb9ef3add0bec62dfcf0ce6ceb9cb9a803f2a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Jan 2019 07:25:35 +0100 Subject: [PATCH 5/7] Loop twice --- freqtrade/resolvers/strategy_resolver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 35c02599a..15451a7d9 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -52,6 +52,9 @@ class StrategyResolver(IResolver): ] for attribute in attributes: self._override_attribute_helper(config, attribute) + + # Loop this list again to have output combined + for attribute in attributes: if attribute in config: logger.info("Strategy using %s: %s", attribute, config[attribute]) From f32232ba9602fb85b38a902aa256f0f192a0eb55 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Jan 2019 07:31:03 +0100 Subject: [PATCH 6/7] Add documentation for stoploss in strategy --- docs/configuration.md | 8 ++++---- docs/stoploss.md | 3 +++ freqtrade/resolvers/strategy_resolver.py | 2 -- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index eb58e4925..44db019d0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -19,10 +19,10 @@ The table below will list all configuration parameters. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. | `process_only_new_candles` | false | No | 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. Can be set either in Configuration or in the strategy. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file. -| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. -| `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). -| `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached. -| `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. +| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). +| `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). +| `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). +| `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). | `unfilledtimeout.buy` | 10 | Yes | 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 | Yes | 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 | Yes | Set the bidding price. More information below. diff --git a/docs/stoploss.md b/docs/stoploss.md index f34050a85..0278e7bbb 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -6,6 +6,9 @@ At this stage the bot contains the following stoploss support modes: 2. trailing stop loss, defined in the configuration 3. trailing stop loss, custom positive loss, defined in configuration +!!! Note + All stoploss properties can be configured in eihter Strategy or configuration. Configuration values override strategy values. + ## Static Stop Loss This is very simple, basically you define a stop loss of x in your strategy file or alternative in the configuration, which diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 15451a7d9..0d1c9f9a0 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -58,8 +58,6 @@ class StrategyResolver(IResolver): if attribute in config: logger.info("Strategy using %s: %s", attribute, config[attribute]) - - # Sort and apply type conversions self.strategy.minimal_roi = OrderedDict(sorted( {int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(), From f7b96d839d644474321f5cca34b79aa28fc8a419 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Jan 2019 09:03:14 +0100 Subject: [PATCH 7/7] Add trailing options to sample-strategy --- user_data/strategies/test_strategy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index f72677e3d..048b398c7 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -42,6 +42,11 @@ class TestStrategy(IStrategy): # 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 = None # Disabled / not configured + # Optimal ticker interval for the strategy ticker_interval = '5m'