diff --git a/docs/configuration.md b/docs/configuration.md index 98953d73f..9c3b20338 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -131,17 +131,11 @@ If it is not set in either Strategy or Configuration, a default of 1000% `{"0": ### Understand stoploss -The `stoploss` configuration parameter is loss in percentage that should trigger a sale. -For example, value `-0.10` will cause immediate sell if the -profit dips below -10% for a given trade. This parameter is optional. - -Most of the strategy files already include the optimal `stoploss` -value. This parameter is optional. If you use it in the configuration file, it will take over the -`stoploss` value from the strategy file. +Go to the [stoploss documentation](stoploss.md) for more details. ### Understand trailing stoploss -Go to the [trailing stoploss Documentation](stoploss.md) for details on trailing stoploss. +Go to the [trailing stoploss Documentation](stoploss.md#trailing-stop-loss) for details on trailing stoploss. ### Understand initial_state diff --git a/docs/stoploss.md b/docs/stoploss.md index cbe4fd3c4..f5e2f8df6 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -1,4 +1,13 @@ -# Stop Loss support +# Stop Loss + +The `stoploss` configuration parameter is loss in percentage that should trigger a sale. +For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. + +Most of the strategy files already include the optimal `stoploss` +value. This parameter is optional. If you use it in the configuration file, it will take over the +`stoploss` value from the strategy file. + +## Stop Loss support At this stage the bot contains the following stoploss support modes: @@ -16,13 +25,12 @@ In case of stoploss on exchange there is another parameter called `stoploss_on_e !!! Note Stoploss on exchange is only supported for Binance as of now. - ## 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 will overwrite the strategy definition. This will basically try to sell your asset, the second the loss exceeds the defined loss. -## Trail Stop Loss +## Trailing Stop Loss The initial value for this stop loss, is defined in your strategy or configuration. Just as you would define your Stop Loss normally. To enable this Feauture all you have to do is to define the configuration element: @@ -63,3 +71,13 @@ 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. Until then, the stoploss remains at the configured`stoploss`. + +## Changing stoploss on open trades + +A stoploss on an open trade can be changed by changing the value in the configuration or strategy and use the `/reload_conf` command (alternatively, completely stopping and restarting the bot also works). + +The new stoploss value will be applied to open trades (and corresponding log-messages will be generated). + +### Limitations + +Stoploss values cannot be changed if `trailing_stop` is enabled and the stoploss has already been adjusted, or if [Edge](edge.md) is enabled (since Edge would recalculate stoploss based on the current market situation). diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index b44775dfb..fd9760bda 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -218,9 +218,12 @@ stoploss = -0.10 ``` This would signify a stoploss of -10%. + +For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md). + If your exchange supports it, it's recommended to also set `"stoploss_on_exchange"` in the order dict, so your stoploss is on the exchange and cannot be missed for network-problems (or other problems). -For more information on order_types please look [here](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md#understand-order_types). +For more information on order_types please look [here](configuration.md#understand-order_types). ### Ticker interval diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3f8c1e106..471e9d218 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -90,6 +90,16 @@ class FreqtradeBot(object): self.rpc.cleanup() persistence.cleanup() + def startup(self) -> None: + """ + Called on startup and after reloading the bot - triggers notifications and + performs startup tasks + """ + self.rpc.startup_messages(self.config, self.pairlists) + if not self.edge: + # Adjust stoploss if it was changed + Trade.stoploss_reinitialization(self.strategy.stoploss) + def process(self) -> bool: """ Queries the persistence layer for open trades and handles them, diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 3d86d4f4d..c844bbc4c 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -422,3 +422,22 @@ class Trade(_DECL_BASE): Query trades from persistence layer """ return Trade.query.filter(Trade.is_open.is_(True)).all() + + @staticmethod + def stoploss_reinitialization(desired_stoploss): + """ + Adjust initial Stoploss to desired stoploss for all open trades. + """ + for trade in Trade.get_open_trades(): + logger.info("Found open trade: %s", trade) + + # skip case if trailing-stop changed the stoploss already. + if (trade.stop_loss == trade.initial_stop_loss + and trade.initial_stop_loss_pct != desired_stoploss): + # Stoploss value got changed + + logger.info(f"Stoploss for {trade} needs adjustment.") + # Force reset of stoploss + trade.stop_loss = None + trade.adjust_stop_loss(trade.open_rate, desired_stoploss) + logger.info(f"new stoploss: {trade.stop_loss}, ") diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index dcc69fcb1..dd0148bd8 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -2,6 +2,7 @@ import json import logging import re +from copy import deepcopy from datetime import datetime from functools import reduce from unittest.mock import MagicMock, PropertyMock @@ -952,9 +953,10 @@ def buy_order_fee(): @pytest.fixture(scope="function") def edge_conf(default_conf): - default_conf['max_open_trades'] = -1 - default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT - default_conf['edge'] = { + conf = deepcopy(default_conf) + conf['max_open_trades'] = -1 + conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT + conf['edge'] = { "enabled": True, "process_throttle_secs": 1800, "calculate_since_number_of_days": 14, @@ -970,7 +972,7 @@ def edge_conf(default_conf): "remove_pumps": False } - return default_conf + return conf @pytest.fixture diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 330a697fc..6566e4036 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -105,6 +105,7 @@ def test_cleanup(mocker, default_conf, caplog) -> None: def test_worker_running(mocker, default_conf, caplog) -> None: mock_throttle = MagicMock() mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle) + mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', MagicMock()) worker = get_patched_worker(mocker, default_conf) @@ -3144,10 +3145,27 @@ def test_get_sell_rate(default_conf, mocker, ticker, order_book_l2) -> None: assert rate == 0.043936 -def test_startup_messages(default_conf, mocker): +def test_startup_state(default_conf, mocker): default_conf['pairlist'] = {'method': 'VolumePairList', 'config': {'number_assets': 20} } mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) worker = get_patched_worker(mocker, default_conf) assert worker.state is State.RUNNING + + +def test_startup_trade_reinit(default_conf, edge_conf, mocker): + + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) + reinit_mock = MagicMock() + mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', reinit_mock) + + ftbot = get_patched_freqtradebot(mocker, default_conf) + ftbot.startup() + assert reinit_mock.call_count == 1 + + reinit_mock.reset_mock() + + ftbot = get_patched_freqtradebot(mocker, edge_conf) + ftbot.startup() + assert reinit_mock.call_count == 0 diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 7e47abf39..bb00fa8f4 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -777,3 +777,63 @@ def test_to_json(default_conf, fee): 'stop_loss_pct': None, 'initial_stop_loss': None, 'initial_stop_loss_pct': None} + + +def test_stoploss_reinitialization(default_conf, fee): + init(default_conf['db_url']) + trade = Trade( + pair='ETH/BTC', + stake_amount=0.001, + fee_open=fee.return_value, + open_date=arrow.utcnow().shift(hours=-2).datetime, + amount=10, + fee_close=fee.return_value, + exchange='bittrex', + open_rate=1, + max_rate=1, + ) + + trade.adjust_stop_loss(trade.open_rate, 0.05, True) + assert trade.stop_loss == 0.95 + assert trade.stop_loss_pct == -0.05 + assert trade.initial_stop_loss == 0.95 + assert trade.initial_stop_loss_pct == -0.05 + Trade.session.add(trade) + + # Lower stoploss + Trade.stoploss_reinitialization(0.06) + + trades = Trade.get_open_trades() + assert len(trades) == 1 + trade_adj = trades[0] + assert trade_adj.stop_loss == 0.94 + assert trade_adj.stop_loss_pct == -0.06 + assert trade_adj.initial_stop_loss == 0.94 + assert trade_adj.initial_stop_loss_pct == -0.06 + + # Raise stoploss + Trade.stoploss_reinitialization(0.04) + + trades = Trade.get_open_trades() + assert len(trades) == 1 + trade_adj = trades[0] + assert trade_adj.stop_loss == 0.96 + assert trade_adj.stop_loss_pct == -0.04 + assert trade_adj.initial_stop_loss == 0.96 + assert trade_adj.initial_stop_loss_pct == -0.04 + + # Trailing stoploss (move stoplos up a bit) + trade.adjust_stop_loss(1.02, 0.04) + assert trade_adj.stop_loss == 0.9792 + assert trade_adj.initial_stop_loss == 0.96 + + Trade.stoploss_reinitialization(0.04) + + trades = Trade.get_open_trades() + assert len(trades) == 1 + trade_adj = trades[0] + # Stoploss should not change in this case. + assert trade_adj.stop_loss == 0.9792 + assert trade_adj.stop_loss_pct == -0.04 + assert trade_adj.initial_stop_loss == 0.96 + assert trade_adj.initial_stop_loss_pct == -0.04 diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 19a570505..c224b4ee5 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -91,7 +91,7 @@ class Worker(object): }) logger.info('Changing state to: %s', state.name) if state == State.RUNNING: - self.freqtrade.rpc.startup_messages(self._config, self.freqtrade.pairlists) + self.freqtrade.startup() if state == State.STOPPED: # Ping systemd watchdog before sleeping in the stopped state