From c0a7725c1fdcf8dadae42ba8a7af4c2dda657f8f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Jul 2018 21:23:35 +0200 Subject: [PATCH 1/5] Add stoploss offset --- freqtrade/constants.py | 1 + freqtrade/strategy/interface.py | 10 +++-- freqtrade/tests/test_freqtradebot.py | 66 +++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 385dac1d1..a2b69b2d7 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -63,6 +63,7 @@ CONF_SCHEMA = { 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'trailing_stop': {'type': 'boolean'}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, + 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'unfilledtimeout': { 'type': 'object', 'properties': { diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index fb8bcd31d..7cf990cf5 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -174,6 +174,7 @@ class IStrategy(ABC): """ Based on current profit of the trade and configured (trailing) stoploss, decides to sell or not + :param current_profit: current profit in percent """ trailing_stop = self.config.get('trailing_stop', False) @@ -199,13 +200,16 @@ class IStrategy(ABC): # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = self.stoploss - if 'trailing_stop_positive' in self.config and current_profit > 0: + stop_loss_value = self.strategy.stoploss + sl_offset = self.config.get('trailing_stop_positive_offset', 0.0) + + if 'trailing_stop_positive' in self.config and current_profit > sl_offset: # Ignore mypy error check in configuration that this is a float stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore logger.debug(f"using positive stop loss mode: {stop_loss_value} " - f"since we have profit {current_profit}") + f"with offset {sl_offset:.4g} " + f"since we have profit {current_profit:.4f}%") trade.adjust_stop_loss(current_rate, stop_loss_value) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index b7ae96048..61907a321 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1805,7 +1805,71 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, caplog, })) # stop-loss not reached, adjusted stoploss assert freqtrade.handle_trade(trade) is False - assert log_has(f'using positive stop loss mode: 0.01 since we have profit 0.26662643', + assert log_has(f'using positive stop loss mode: 0.01 with offset 0 ' + f'since we have profit 0.2666%', + caplog.record_tuples) + assert log_has(f'adjusted stop loss', caplog.record_tuples) + assert trade.stop_loss == 0.0000138501 + + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.000002, + 'ask': buy_price + 0.000002, + 'last': buy_price + 0.000002 + })) + # Lower price again (but still positive) + assert freqtrade.handle_trade(trade) is True + assert log_has( + f'HIT STOP: current price at {buy_price + 0.000002:.6f}, ' + f'stop loss is {trade.stop_loss:.6f}, ' + f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples) + + +def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, mocker) -> None: + """ + Test sell_profit_only feature when enabled and we have a loss + """ + buy_price = limit_buy_order['price'] + patch_get_signal(mocker) + patch_RPCManager(mocker) + patch_coinmarketcap(mocker) + mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': buy_price - 0.000001, + 'ask': buy_price - 0.000001, + 'last': buy_price - 0.000001 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + ) + + conf = deepcopy(default_conf) + conf['trailing_stop'] = True + conf['trailing_stop_positive'] = 0.01 + conf['trailing_stop_positive_offset'] = 0.011 + freqtrade = FreqtradeBot(conf) + 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 + + # Raise ticker above buy price + mocker.patch('freqtrade.exchange.Exchange.get_ticker', + MagicMock(return_value={ + 'bid': buy_price + 0.000003, + 'ask': buy_price + 0.000003, + 'last': buy_price + 0.000003 + })) + # stop-loss not reached, adjusted stoploss + assert freqtrade.handle_trade(trade) is False + assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 ' + f'since we have profit 0.2666%', caplog.record_tuples) assert log_has(f'adjusted stop loss', caplog.record_tuples) assert trade.stop_loss == 0.0000138501 From 6a3c8e3933a911ba7df6d9694be235a78579a78a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Jul 2018 21:23:45 +0200 Subject: [PATCH 2/5] update docs for trailing stoploss offset --- docs/configuration.md | 1 + docs/stoploss.md | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index ddfa9834e..d4ccedf84 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -27,6 +27,7 @@ The table below will list all configuration parameters. | `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_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached. +| `trailing_stoploss_positve_offset` | 0 | No | Offset on when to apply `trailing_stoploss_positive`. Percentage value which should be positive. | `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 db4433a02..9740672cd 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -35,14 +35,17 @@ basically what this means is that your stop loss will be adjusted to be always b ### Custom positive loss -Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your buy turns positive, -the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the -black, it will be changed to be only a 1% stop loss +Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your profit surpasses a certain percentage, +the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you have 1.1% profit, +it will be changed to be only a 1% stop loss, which trails the green candles until it goes below them. -This can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true. +Both values can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true. ``` json "trailing_stop_positive": 0.01, + "trailing_stop_positive_offset": 0.011, ``` -The 0.01 would translate to a 1% stop loss, once you hit profit. +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 higher than your minimal ROI, otherwise minimal ROI will apply first and sell your trade. From 365ba98131c57a90280f8bf615e18d79c67eee2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 16 Jul 2018 21:30:50 +0200 Subject: [PATCH 3/5] add option to full_json example --- config_full.json.example | 1 + 1 file changed, 1 insertion(+) diff --git a/config_full.json.example b/config_full.json.example index 4003b1c5c..b0714535f 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -7,6 +7,7 @@ "ticker_interval": "5m", "trailing_stop": false, "trailing_stop_positive": 0.005, + "trailing_stop_positive_offset": 0.0051, "minimal_roi": { "40": 0.0, "30": 0.01, From 060469fefc774b0dd0110bfbd76106b354c6fce2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Jul 2018 20:12:20 +0200 Subject: [PATCH 4/5] Add stuff after rebase --- freqtrade/strategy/interface.py | 2 +- freqtrade/tests/test_freqtradebot.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7cf990cf5..61ad41891 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -200,7 +200,7 @@ class IStrategy(ABC): # check if we have a special stop loss for positive condition # and if profit is positive - stop_loss_value = self.strategy.stoploss + stop_loss_value = self.stoploss sl_offset = self.config.get('trailing_stop_positive_offset', 0.0) if 'trailing_stop_positive' in self.config and current_profit > sl_offset: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 61907a321..cf1f35e3d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -45,7 +45,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: :param value: which value IStrategy.get_signal() must return :return: None """ - freqtrade.strategy.get_signal = lambda e, s, t: value + freqtrade.get_signal = lambda e, s, t: value def patch_RPCManager(mocker) -> MagicMock: @@ -1833,7 +1833,6 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) - mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), @@ -1851,6 +1850,8 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m conf['trailing_stop_positive'] = 0.01 conf['trailing_stop_positive_offset'] = 0.011 freqtrade = FreqtradeBot(conf) + freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True + freqtrade.create_trade() trade = Trade.query.first() From 1b2bfad34827298dd3f9b3bf46bd97dab8c4b83a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Jul 2018 20:36:49 +0200 Subject: [PATCH 5/5] Fix wrong test --- freqtrade/tests/test_freqtradebot.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index cf1f35e3d..9448f8c68 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -45,7 +45,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None: :param value: which value IStrategy.get_signal() must return :return: None """ - freqtrade.get_signal = lambda e, s, t: value + freqtrade.strategy.get_signal = lambda e, s, t: value def patch_RPCManager(mocker) -> MagicMock: @@ -1830,7 +1830,6 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m Test sell_profit_only feature when enabled and we have a loss """ buy_price = limit_buy_order['price'] - patch_get_signal(mocker) patch_RPCManager(mocker) patch_coinmarketcap(mocker) mocker.patch.multiple( @@ -1850,8 +1849,8 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, m conf['trailing_stop_positive'] = 0.01 conf['trailing_stop_positive_offset'] = 0.011 freqtrade = FreqtradeBot(conf) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True - + patch_get_signal(freqtrade) + freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False freqtrade.create_trade() trade = Trade.query.first()