From c122eab77b52d0cc9fd942f5a130ad0b04a1e236 Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 9 Mar 2019 20:13:35 +0100 Subject: [PATCH 01/17] added trailing_only_offset_is_reached option --- freqtrade/strategy/interface.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1d6147357..29976fb4a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -331,7 +331,11 @@ class IStrategy(ABC): f"with offset {sl_offset:.4g} " f"since we have profit {current_profit:.4f}%") - trade.adjust_stop_loss(current_rate, stop_loss_value) + # if trailing_only_offset_is_reached is true, + # we update trailing stoploss only if offset is reached. + tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) + if tsl_only_offset and current_profit > sl_offset: + trade.adjust_stop_loss(current_rate, stop_loss_value) return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) From 9c1c962aa7394ce4e409e9957330de9c8da0adcd Mon Sep 17 00:00:00 2001 From: misagh Date: Sat, 9 Mar 2019 20:30:56 +0100 Subject: [PATCH 02/17] if condition fixed --- freqtrade/strategy/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 29976fb4a..32efdeb17 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -334,7 +334,7 @@ class IStrategy(ABC): # if trailing_only_offset_is_reached is true, # we update trailing stoploss only if offset is reached. tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False) - if tsl_only_offset and current_profit > sl_offset: + if not (tsl_only_offset and current_profit < sl_offset): trade.adjust_stop_loss(current_rate, stop_loss_value) return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE) From 0467004144bc35ca932d1aee5546a2f31825f260 Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 10 Mar 2019 15:54:46 +0100 Subject: [PATCH 03/17] added trailing_only_offset_is_reached to full config --- config_full.json.example | 1 + 1 file changed, 1 insertion(+) diff --git a/config_full.json.example b/config_full.json.example index 0f46a62e3..be4e02039 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -9,6 +9,7 @@ "trailing_stop": false, "trailing_stop_positive": 0.005, "trailing_stop_positive_offset": 0.0051, + "trailing_only_offset_is_reached": false, "minimal_roi": { "40": 0.0, "30": 0.01, From ca496c13b8f037f0b0f99021101864fa61db876e Mon Sep 17 00:00:00 2001 From: misagh Date: Sun, 10 Mar 2019 17:11:28 +0100 Subject: [PATCH 04/17] TSL only offset test added --- freqtrade/tests/test_freqtradebot.py | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 2f66a5153..3be0e72c0 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2512,6 +2512,72 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value +def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, + caplog, mocker, markets) -> None: + buy_price = limit_buy_order['price'] + # buy_price: 0.00001099 + + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=MagicMock(return_value={ + 'bid': buy_price, + 'ask': buy_price, + 'last': buy_price + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + get_markets=markets, + ) + + default_conf['trailing_stop'] = True + default_conf['trailing_stop_positive'] = 0.05 + default_conf['trailing_stop_positive_offset'] = 0.055 + default_conf['trailing_only_offset_is_reached'] = True + + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) + freqtrade.create_trade() + + trade = Trade.query.first() + trade.update(limit_buy_order) + caplog.set_level(logging.DEBUG) + # stop-loss not reached + assert freqtrade.handle_trade(trade) is False + assert trade.stop_loss == 0.0000098910 + + # Raise ticker above buy price + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.0000004, + 'ask': buy_price + 0.0000004, + 'last': buy_price + 0.0000004 + })) + + # stop-loss should not be adjusted as offset is not reached yet + assert freqtrade.handle_trade(trade) is False + + assert not log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.0000098910 + + # price rises above the offset (rises 12% when the offset is 5.5%) + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.0000014, + 'ask': buy_price + 0.0000014, + 'last': buy_price + 0.0000014 + })) + + assert freqtrade.handle_trade(trade) is False + assert log_has(f'using positive stop loss mode: 0.05 with offset 0.055 ' + f'since we have profit 0.1218%', + caplog.record_tuples) + assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.0000117705 + + def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None: patch_RPCManager(mocker) From 643262bc6a48a5761f7c42c73400b630026e30b8 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 13:03:29 +0100 Subject: [PATCH 05/17] add trailing stop loss config validator --- freqtrade/exchange/exchange.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 32d952542..96eebecc2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -111,6 +111,8 @@ class Exchange(object): self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {})) + self.validate_trailing_stoploss(config) + if config.get('ticker_interval'): # Check if timeframe is available self.validate_timeframes(config['ticker_interval']) @@ -257,6 +259,30 @@ class Exchange(object): raise OperationalException( f'Time in force policies are not supporetd for {self.name} yet.') + def validate_trailing_stoploss(self, config) -> None: + """ + Validates the trailing stoploss configuration + """ + + tsl = config.get('trailing_stop', False) + # Skip if trailing stoploss is not activated + if not tsl: + return + + tsl_positive = float(config.get('trailing_stop_positive', 0)) + tsl_offset = float(config.get('trailing_stop_positive_offset', 0)) + tsl_only_offset = config.get('trailing_only_offset_is_reached', False) + + if tsl_only_offset: + if tsl_positive == 0.0: + raise OperationalException( + f'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config') + if tsl_positive > 0 and tsl_offset > 0 and tsl_offset <= tsl_positive: + raise OperationalException( + f'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config') + def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. From 3e40f5c588a13d9cdac0598e0131ea6f9692e04d Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 13:09:27 +0100 Subject: [PATCH 06/17] if condition simplified --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 96eebecc2..8c4315906 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -278,10 +278,10 @@ class Exchange(object): raise OperationalException( f'The config trailing_only_offset_is_reached need ' 'trailing_stop_positive_offset to be more than 0 in your config') - if tsl_positive > 0 and tsl_offset > 0 and tsl_offset <= tsl_positive: + if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: raise OperationalException( f'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config') + 'to be greater than trailing_stop_positive_offset in your config') def exchange_has(self, endpoint: str) -> bool: """ From 36e95bc8689081a4a775808fb6531ba56a3f22a3 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 13:10:59 +0100 Subject: [PATCH 07/17] unnecessary variable removed --- freqtrade/exchange/exchange.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8c4315906..13ab51226 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -263,10 +263,8 @@ class Exchange(object): """ Validates the trailing stoploss configuration """ - - tsl = config.get('trailing_stop', False) # Skip if trailing stoploss is not activated - if not tsl: + if not config.get('trailing_stop', False): return tsl_positive = float(config.get('trailing_stop_positive', 0)) From f55d75e7fcb15f7a27c7cfd51981f63697019803 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:35:44 +0100 Subject: [PATCH 08/17] TSL validation tests added --- freqtrade/exchange/exchange.py | 8 +++---- freqtrade/tests/exchange/test_exchange.py | 27 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 13ab51226..88f255c85 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -274,12 +274,12 @@ class Exchange(object): if tsl_only_offset: if tsl_positive == 0.0: raise OperationalException( - f'The config trailing_only_offset_is_reached need ' - 'trailing_stop_positive_offset to be more than 0 in your config') + f'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.') if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: raise OperationalException( - f'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config') + f'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.') def exchange_has(self, endpoint: str) -> bool: """ diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index ff36ab91c..16e5e693b 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -432,6 +432,33 @@ def test_validate_order_types(default_conf, mocker): Exchange(default_conf) +def test_validate_tsl(default_conf, mocker): + api_mock = MagicMock() + mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) + mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + default_conf['trailing_stop'] = True + default_conf['trailing_stop_positive'] = 0 + default_conf['trailing_stop_positive_offset'] = 0 + default_conf['trailing_only_offset_is_reached'] = False + + Exchange(default_conf) + + default_conf['trailing_only_offset_is_reached'] = True + with pytest.raises(OperationalException, + match=r'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.'): + Exchange(default_conf) + + default_conf['trailing_stop_positive_offset'] = 0.01 + default_conf['trailing_stop_positive'] = 0.015 + with pytest.raises(OperationalException, + match=r'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.'): + Exchange(default_conf) + + def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) From a772ab323e3212d7eaf91b881a358dcd01c856ba Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:43:53 +0100 Subject: [PATCH 09/17] adding the option to resolver --- freqtrade/constants.py | 1 + freqtrade/resolvers/strategy_resolver.py | 25 ++++++++++++------------ freqtrade/strategy/interface.py | 1 + 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 4d0907d78..f0e9f7490 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -73,6 +73,7 @@ CONF_SCHEMA = { 'trailing_stop': {'type': 'boolean'}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, + 'trailing_only_offset_is_reached': {'type': 'boolean'}, 'unfilledtimeout': { 'type': 'object', 'properties': { diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index c49da9205..60d1fe21c 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -46,18 +46,19 @@ class StrategyResolver(IResolver): # Set attributes # Check if we need to override configuration # (Attribute name, default, experimental) - attributes = [("minimal_roi", None, False), - ("ticker_interval", None, False), - ("stoploss", None, False), - ("trailing_stop", None, False), - ("trailing_stop_positive", None, False), - ("trailing_stop_positive_offset", 0.0, False), - ("process_only_new_candles", None, False), - ("order_types", None, False), - ("order_time_in_force", None, False), - ("use_sell_signal", False, True), - ("sell_profit_only", False, True), - ("ignore_roi_if_buy_signal", False, True), + attributes = [("minimal_roi", None, False), + ("ticker_interval", None, False), + ("stoploss", None, False), + ("trailing_stop", None, False), + ("trailing_stop_positive", None, False), + ("trailing_stop_positive_offset", 0.0, False), + ("trailing_only_offset_is_reached", None, False), + ("process_only_new_candles", None, False), + ("order_types", None, False), + ("order_time_in_force", None, False), + ("use_sell_signal", False, True), + ("sell_profit_only", False, True), + ("ignore_roi_if_buy_signal", False, True), ] for attribute, default, experimental in attributes: if experimental: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 32efdeb17..41dcb8c57 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -73,6 +73,7 @@ class IStrategy(ABC): trailing_stop: bool = False trailing_stop_positive: float trailing_stop_positive_offset: float + trailing_only_offset_is_reached = False # associated ticker interval ticker_interval: str From 8d5cc42ef5a30f806fd0110e6c75c3ece12fb0de Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:46:21 +0100 Subject: [PATCH 10/17] configuration doc added --- docs/configuration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index d7e774595..59816ec4d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -26,6 +26,7 @@ Mandatory Parameters are marked as **Required**. | `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-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-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-strategy). +| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-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). @@ -319,7 +320,7 @@ section of the configuration. * `VolumePairList` * Formerly available as `--dynamic-whitelist []`. This command line option is deprecated and should no longer be used. - * It selects `number_assets` top pairs based on `sort_key`, which can be one of + * It selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`. * There is a possibility to filter low-value coins that would not allow setting a stop loss (set `precision_filter` parameter to `true` for this). From 0bcf50f1b57ecc63996b8254bfd2bba0b2bdf655 Mon Sep 17 00:00:00 2001 From: misagh Date: Tue, 12 Mar 2019 15:48:30 +0100 Subject: [PATCH 11/17] added to stoploss doc --- docs/stoploss.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/stoploss.md b/docs/stoploss.md index 0726aebbc..62276e7cc 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -55,8 +55,11 @@ Both values can be configured in the main configuration file and requires `"trai ``` json "trailing_stop_positive": 0.01, "trailing_stop_positive_offset": 0.011, + "trailing_only_offset_is_reached": false ``` The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit. You should also make sure to have this value (`trailing_stop_positive_offset`) lower than your minimal ROI, otherwise minimal ROI will apply first and sell your trade. + +If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. From 3c99e3b7c776d55c8de84976c13ce440cb6fa726 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:00:28 +0100 Subject: [PATCH 12/17] test adapted to new market refactoring --- freqtrade/tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 796b4411e..fc7c48663 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -2504,7 +2504,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee, }), buy=MagicMock(return_value={'id': limit_buy_order['id']}), get_fee=fee, - get_markets=markets, + markets=PropertyMock(return_value=markets), ) default_conf['trailing_stop'] = True From 29305dd0704ade08a614fce4f528fcb224cf1244 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:01:03 +0100 Subject: [PATCH 13/17] config validation moved to configuration file --- freqtrade/configuration.py | 32 ++++++++++++++++++++- freqtrade/tests/exchange/test_exchange.py | 27 ------------------ freqtrade/tests/test_configuration.py | 34 +++++++++++++++++++++-- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index e96305993..585704b2d 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -58,6 +58,7 @@ class Configuration(object): config['internals'] = {} logger.info('Validating configuration ...') + self._validate_config_schema(config) self._validate_config(config) # Set strategy if not specified in config and or if it's non default @@ -291,7 +292,7 @@ class Configuration(object): return config - def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]: + def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]: """ Validate the configuration follow the Config Schema :param conf: Config in JSON format @@ -309,6 +310,35 @@ class Configuration(object): best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message ) + def _validate_config(self, conf: Dict[str, Any]) -> None: + """ + Validate the configuration consistency + :param conf: Config in JSON format + :return: Returns None if everything is ok, otherwise throw an exception + """ + + # validating trailing stoploss + self._validate_trailing_stoploss(conf) + + def _validate_trailing_stoploss(self, conf: Dict[str, Any]) -> None: + # Skip if trailing stoploss is not activated + if not conf.get('trailing_stop', False): + return + + tsl_positive = float(conf.get('trailing_stop_positive', 0)) + tsl_offset = float(conf.get('trailing_stop_positive_offset', 0)) + tsl_only_offset = conf.get('trailing_only_offset_is_reached', False) + + if tsl_only_offset: + if tsl_positive == 0.0: + raise OperationalException( + f'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.') + if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: + raise OperationalException( + f'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.') + def get_config(self) -> Dict[str, Any]: """ Return the config. Use this method to get the bot config diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index c71c1580f..7c757df09 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -449,33 +449,6 @@ def test_validate_order_types(default_conf, mocker): Exchange(default_conf) -def test_validate_tsl(default_conf, mocker): - api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') - default_conf['trailing_stop'] = True - default_conf['trailing_stop_positive'] = 0 - default_conf['trailing_stop_positive_offset'] = 0 - default_conf['trailing_only_offset_is_reached'] = False - - Exchange(default_conf) - - default_conf['trailing_only_offset_is_reached'] = True - with pytest.raises(OperationalException, - match=r'The config trailing_only_offset_is_reached need ' - 'trailing_stop_positive_offset to be more than 0 in your config.'): - Exchange(default_conf) - - default_conf['trailing_stop_positive_offset'] = 0.01 - default_conf['trailing_stop_positive'] = 0.015 - with pytest.raises(OperationalException, - match=r'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config.'): - Exchange(default_conf) - - def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 51098baaa..3b7b1285c 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,6 +13,7 @@ from freqtrade import OperationalException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL +from freqtrade.exchange import Exchange from freqtrade.state import RunMode from freqtrade.tests.conftest import log_has @@ -22,7 +23,7 @@ def test_load_config_invalid_pair(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*does not match.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_missing_attributes(default_conf) -> None: @@ -30,7 +31,7 @@ def test_load_config_missing_attributes(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_incorrect_stake_amount(default_conf) -> None: @@ -38,7 +39,7 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*\'fake\' does not match \'unlimited\'.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_file(default_conf, mocker, caplog) -> None: @@ -573,3 +574,30 @@ def test__create_datadir(mocker, default_conf, caplog) -> None: cfg._create_datadir(default_conf, '/foo/bar') assert md.call_args[0][0] == "/foo/bar" assert log_has('Created data directory: /foo/bar', caplog.record_tuples) + + +def test_validate_tsl(default_conf, mocker): + mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + default_conf['trailing_stop'] = True + default_conf['trailing_stop_positive'] = 0 + default_conf['trailing_stop_positive_offset'] = 0 + default_conf['trailing_only_offset_is_reached'] = False + + Exchange(default_conf) + + default_conf['trailing_only_offset_is_reached'] = True + with pytest.raises(OperationalException, + match=r'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.'): + Exchange(default_conf) + + default_conf['trailing_stop_positive_offset'] = 0.01 + default_conf['trailing_stop_positive'] = 0.015 + with pytest.raises(OperationalException, + match=r'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.'): + Exchange(default_conf) From b5034cf53579101ae9af06ad22e130cddf097ecb Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:04:41 +0100 Subject: [PATCH 14/17] TSL validator removed from exchange --- freqtrade/exchange/exchange.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b82959680..33f62f2f7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -118,7 +118,6 @@ class Exchange(object): self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_ordertypes(config.get('order_types', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {})) - self.validate_trailing_stoploss(config) if config.get('ticker_interval'): # Check if timeframe is available @@ -285,28 +284,6 @@ class Exchange(object): raise OperationalException( f'Time in force policies are not supporetd for {self.name} yet.') - def validate_trailing_stoploss(self, config) -> None: - """ - Validates the trailing stoploss configuration - """ - # Skip if trailing stoploss is not activated - if not config.get('trailing_stop', False): - return - - tsl_positive = float(config.get('trailing_stop_positive', 0)) - tsl_offset = float(config.get('trailing_stop_positive_offset', 0)) - tsl_only_offset = config.get('trailing_only_offset_is_reached', False) - - if tsl_only_offset: - if tsl_positive == 0.0: - raise OperationalException( - f'The config trailing_only_offset_is_reached need ' - 'trailing_stop_positive_offset to be more than 0 in your config.') - if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: - raise OperationalException( - f'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config.') - def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. From edf2cd0b926e51522f4b50e2d1fc22566a903e3d Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 14 Mar 2019 09:26:31 +0100 Subject: [PATCH 15/17] configuration test fixed --- freqtrade/tests/test_configuration.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 3b7b1285c..dace9904b 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,7 +13,6 @@ from freqtrade import OperationalException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL -from freqtrade.exchange import Exchange from freqtrade.state import RunMode from freqtrade.tests.conftest import log_has @@ -576,28 +575,22 @@ def test__create_datadir(mocker, default_conf, caplog) -> None: assert log_has('Created data directory: /foo/bar', caplog.record_tuples) -def test_validate_tsl(default_conf, mocker): - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') +def test_validate_tsl(default_conf): default_conf['trailing_stop'] = True default_conf['trailing_stop_positive'] = 0 default_conf['trailing_stop_positive_offset'] = 0 - default_conf['trailing_only_offset_is_reached'] = False - - Exchange(default_conf) default_conf['trailing_only_offset_is_reached'] = True with pytest.raises(OperationalException, match=r'The config trailing_only_offset_is_reached need ' 'trailing_stop_positive_offset to be more than 0 in your config.'): - Exchange(default_conf) + configuration = Configuration(Namespace()) + configuration._validate_config(default_conf) default_conf['trailing_stop_positive_offset'] = 0.01 default_conf['trailing_stop_positive'] = 0.015 with pytest.raises(OperationalException, match=r'The config trailing_stop_positive_offset need ' 'to be greater than trailing_stop_positive_offset in your config.'): - Exchange(default_conf) + configuration = Configuration(Namespace()) + configuration._validate_config(default_conf) From d42ebab5750dc270c868717776ffee60df24c05a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 10:38:25 +0100 Subject: [PATCH 16/17] Rename function and add test --- freqtrade/configuration.py | 10 +++++----- freqtrade/tests/test_configuration.py | 13 +++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 585704b2d..d98b2ba21 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -59,7 +59,7 @@ class Configuration(object): logger.info('Validating configuration ...') self._validate_config_schema(config) - self._validate_config(config) + self._validate_config_consistency(config) # Set strategy if not specified in config and or if it's non default if self.args.strategy != constants.DEFAULT_STRATEGY or not config.get('strategy'): @@ -310,11 +310,11 @@ class Configuration(object): best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message ) - def _validate_config(self, conf: Dict[str, Any]) -> None: + def _validate_config_consistency(self, conf: Dict[str, Any]) -> None: """ Validate the configuration consistency :param conf: Config in JSON format - :return: Returns None if everything is ok, otherwise throw an exception + :return: Returns None if everything is ok, otherwise throw an OperationalException """ # validating trailing stoploss @@ -332,11 +332,11 @@ class Configuration(object): if tsl_only_offset: if tsl_positive == 0.0: raise OperationalException( - f'The config trailing_only_offset_is_reached need ' + f'The config trailing_only_offset_is_reached needs ' 'trailing_stop_positive_offset to be more than 0 in your config.') if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: raise OperationalException( - f'The config trailing_stop_positive_offset need ' + f'The config trailing_stop_positive_offset needs ' 'to be greater than trailing_stop_positive_offset in your config.') def get_config(self) -> Dict[str, Any]: diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index dace9904b..21547d205 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -582,15 +582,20 @@ def test_validate_tsl(default_conf): default_conf['trailing_only_offset_is_reached'] = True with pytest.raises(OperationalException, - match=r'The config trailing_only_offset_is_reached need ' + match=r'The config trailing_only_offset_is_reached needs ' 'trailing_stop_positive_offset to be more than 0 in your config.'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_consistency(default_conf) default_conf['trailing_stop_positive_offset'] = 0.01 default_conf['trailing_stop_positive'] = 0.015 with pytest.raises(OperationalException, - match=r'The config trailing_stop_positive_offset need ' + match=r'The config trailing_stop_positive_offset needs ' 'to be greater than trailing_stop_positive_offset in your config.'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_consistency(default_conf) + + default_conf['trailing_stop_positive'] = 0.01 + default_conf['trailing_stop_positive_offset'] = 0.015 + Configuration(Namespace()) + configuration._validate_config_consistency(default_conf) From a233a8cc820adb3d0533b77e24bd43f6ec2e6daf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Mar 2019 10:38:32 +0100 Subject: [PATCH 17/17] Be explicit in the documentation --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 62276e7cc..cbe4fd3c4 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -62,4 +62,4 @@ The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit. You should also make sure to have this value (`trailing_stop_positive_offset`) lower than your minimal ROI, otherwise minimal ROI will apply first and sell your trade. -If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. +If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured`stoploss`.