Merge pull request #1868 from freqtrade/stoploss_restart
Stoploss restart
This commit is contained in:
commit
1f3406b29b
@ -131,17 +131,11 @@ If it is not set in either Strategy or Configuration, a default of 1000% `{"0":
|
|||||||
|
|
||||||
### Understand stoploss
|
### Understand stoploss
|
||||||
|
|
||||||
The `stoploss` configuration parameter is loss in percentage that should trigger a sale.
|
Go to the [stoploss documentation](stoploss.md) for more details.
|
||||||
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.
|
|
||||||
|
|
||||||
### Understand trailing stoploss
|
### 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
|
### Understand initial_state
|
||||||
|
|
||||||
|
@ -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:
|
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
|
!!! Note
|
||||||
Stoploss on exchange is only supported for Binance as of now.
|
Stoploss on exchange is only supported for Binance as of now.
|
||||||
|
|
||||||
|
|
||||||
## Static Stop Loss
|
## 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
|
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.
|
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.
|
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:
|
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.
|
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`.
|
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).
|
||||||
|
@ -218,9 +218,12 @@ stoploss = -0.10
|
|||||||
```
|
```
|
||||||
|
|
||||||
This would signify a stoploss of -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).
|
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
|
### Ticker interval
|
||||||
|
|
||||||
|
@ -90,6 +90,16 @@ class FreqtradeBot(object):
|
|||||||
self.rpc.cleanup()
|
self.rpc.cleanup()
|
||||||
persistence.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:
|
def process(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Queries the persistence layer for open trades and handles them,
|
Queries the persistence layer for open trades and handles them,
|
||||||
|
@ -422,3 +422,22 @@ class Trade(_DECL_BASE):
|
|||||||
Query trades from persistence layer
|
Query trades from persistence layer
|
||||||
"""
|
"""
|
||||||
return Trade.query.filter(Trade.is_open.is_(True)).all()
|
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}, ")
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from copy import deepcopy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
@ -952,9 +953,10 @@ def buy_order_fee():
|
|||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def edge_conf(default_conf):
|
def edge_conf(default_conf):
|
||||||
default_conf['max_open_trades'] = -1
|
conf = deepcopy(default_conf)
|
||||||
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
conf['max_open_trades'] = -1
|
||||||
default_conf['edge'] = {
|
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||||
|
conf['edge'] = {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"process_throttle_secs": 1800,
|
"process_throttle_secs": 1800,
|
||||||
"calculate_since_number_of_days": 14,
|
"calculate_since_number_of_days": 14,
|
||||||
@ -970,7 +972,7 @@ def edge_conf(default_conf):
|
|||||||
"remove_pumps": False
|
"remove_pumps": False
|
||||||
}
|
}
|
||||||
|
|
||||||
return default_conf
|
return conf
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -105,6 +105,7 @@ def test_cleanup(mocker, default_conf, caplog) -> None:
|
|||||||
def test_worker_running(mocker, default_conf, caplog) -> None:
|
def test_worker_running(mocker, default_conf, caplog) -> None:
|
||||||
mock_throttle = MagicMock()
|
mock_throttle = MagicMock()
|
||||||
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
|
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
|
||||||
|
mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', MagicMock())
|
||||||
|
|
||||||
worker = get_patched_worker(mocker, default_conf)
|
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
|
assert rate == 0.043936
|
||||||
|
|
||||||
|
|
||||||
def test_startup_messages(default_conf, mocker):
|
def test_startup_state(default_conf, mocker):
|
||||||
default_conf['pairlist'] = {'method': 'VolumePairList',
|
default_conf['pairlist'] = {'method': 'VolumePairList',
|
||||||
'config': {'number_assets': 20}
|
'config': {'number_assets': 20}
|
||||||
}
|
}
|
||||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||||
worker = get_patched_worker(mocker, default_conf)
|
worker = get_patched_worker(mocker, default_conf)
|
||||||
assert worker.state is State.RUNNING
|
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
|
||||||
|
@ -777,3 +777,63 @@ def test_to_json(default_conf, fee):
|
|||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
'initial_stop_loss': None,
|
'initial_stop_loss': None,
|
||||||
'initial_stop_loss_pct': 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
|
||||||
|
@ -91,7 +91,7 @@ class Worker(object):
|
|||||||
})
|
})
|
||||||
logger.info('Changing state to: %s', state.name)
|
logger.info('Changing state to: %s', state.name)
|
||||||
if state == State.RUNNING:
|
if state == State.RUNNING:
|
||||||
self.freqtrade.rpc.startup_messages(self._config, self.freqtrade.pairlists)
|
self.freqtrade.startup()
|
||||||
|
|
||||||
if state == State.STOPPED:
|
if state == State.STOPPED:
|
||||||
# Ping systemd watchdog before sleeping in the stopped state
|
# Ping systemd watchdog before sleeping in the stopped state
|
||||||
|
Loading…
Reference in New Issue
Block a user