Merge pull request #1474 from mishaker/tsl_on_exchange
Making trailing stoploss compatible with stoploss on exchange
This commit is contained in:
commit
c2578c7321
@ -37,7 +37,8 @@
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": "false"
|
||||
"stoploss_on_exchange": "false",
|
||||
"stoploss_on_exchange_interval": 60
|
||||
},
|
||||
"order_time_in_force": {
|
||||
"buy": "gtc",
|
||||
|
@ -144,10 +144,10 @@ end up paying more then would probably have been necessary.
|
||||
|
||||
### Understand order_types
|
||||
|
||||
`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled.
|
||||
`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss).
|
||||
This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations.
|
||||
|
||||
If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start.
|
||||
If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start.
|
||||
The below is the default which is used if this is not configured in either Strategy or configuration.
|
||||
|
||||
```python
|
||||
@ -155,7 +155,8 @@ The below is the default which is used if this is not configured in either Strat
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": False
|
||||
"stoploss_on_exchange": False,
|
||||
"stoploss_on_exchange_interval": 60
|
||||
},
|
||||
```
|
||||
|
||||
@ -163,6 +164,9 @@ The below is the default which is used if this is not configured in either Strat
|
||||
Not all exchanges support "market" orders.
|
||||
The following message will be shown if your exchange does not support market orders: `"Exchange <yourexchange> does not support market orders."`
|
||||
|
||||
!!! Note
|
||||
stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. For more information about how stoploss works please read [the stoploss documentation](stoploss.md).
|
||||
|
||||
### Understand order_time_in_force
|
||||
`order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:<br/>
|
||||
**GTC (Goog Till Canceled):**
|
||||
|
@ -2,12 +2,20 @@
|
||||
|
||||
At this stage the bot contains the following stoploss support modes:
|
||||
|
||||
1. static stop loss, defined in either the strategy or configuration
|
||||
2. trailing stop loss, defined in the configuration
|
||||
3. trailing stop loss, custom positive loss, defined in configuration
|
||||
1. static stop loss, defined in either the strategy or configuration.
|
||||
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.
|
||||
All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values.
|
||||
|
||||
Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled.
|
||||
|
||||
In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute).
|
||||
|
||||
!!! Note
|
||||
Stoploss on exchange is only supported for Binance as of now.
|
||||
|
||||
|
||||
## Static Stop Loss
|
||||
|
||||
|
@ -112,7 +112,8 @@ CONF_SCHEMA = {
|
||||
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'stoploss_on_exchange': {'type': 'boolean'}
|
||||
'stoploss_on_exchange': {'type': 'boolean'},
|
||||
'stoploss_on_exchange_interval': {'type': 'boolean'}
|
||||
},
|
||||
'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
|
||||
},
|
||||
@ -137,7 +138,7 @@ CONF_SCHEMA = {
|
||||
'pairlist': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS},
|
||||
'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS},
|
||||
'config': {'type': 'object'}
|
||||
},
|
||||
'required': ['method']
|
||||
|
@ -402,8 +402,11 @@ class Exchange(object):
|
||||
return self._dry_run_open_orders[order_id]
|
||||
|
||||
try:
|
||||
return self._api.create_order(pair, 'stop_loss_limit', 'sell',
|
||||
amount, rate, {'stopPrice': stop_price})
|
||||
order = self._api.create_order(pair, 'stop_loss_limit', 'sell',
|
||||
amount, rate, {'stopPrice': stop_price})
|
||||
logger.info('stoploss limit order added for %s. '
|
||||
'stop price: %s. limit: %s' % (pair, stop_price, rate))
|
||||
return order
|
||||
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise DependencyException(
|
||||
|
@ -613,7 +613,7 @@ class FreqtradeBot(object):
|
||||
def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
|
||||
"""
|
||||
Check if trade is fulfilled in which case the stoploss
|
||||
on exchange should be added immediately if stoploss on exchnage
|
||||
on exchange should be added immediately if stoploss on exchange
|
||||
is enabled.
|
||||
"""
|
||||
|
||||
@ -630,13 +630,14 @@ class FreqtradeBot(object):
|
||||
stop_price = trade.open_rate * (1 + stoploss)
|
||||
|
||||
# limit price should be less than stop price.
|
||||
# 0.98 is arbitrary here.
|
||||
limit_price = stop_price * 0.98
|
||||
# 0.99 is arbitrary here.
|
||||
limit_price = stop_price * 0.99
|
||||
|
||||
stoploss_order_id = self.exchange.stoploss_limit(
|
||||
pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
|
||||
)['id']
|
||||
trade.stoploss_order_id = str(stoploss_order_id)
|
||||
trade.stoploss_last_update = datetime.now()
|
||||
|
||||
# Or the trade open and there is already a stoploss on exchange.
|
||||
# so we check if it is hit ...
|
||||
@ -647,10 +648,38 @@ class FreqtradeBot(object):
|
||||
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
|
||||
trade.update(order)
|
||||
result = True
|
||||
else:
|
||||
result = False
|
||||
elif self.config.get('trailing_stop', False):
|
||||
# if trailing stoploss is enabled we check if stoploss value has changed
|
||||
# in which case we cancel stoploss order and put another one with new
|
||||
# value immediately
|
||||
self.handle_trailing_stoploss_on_exchange(trade, order)
|
||||
|
||||
return result
|
||||
|
||||
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
|
||||
"""
|
||||
Check to see if stoploss on exchange should be updated
|
||||
in case of trailing stoploss on exchange
|
||||
:param Trade: Corresponding Trade
|
||||
:param order: Current on exchange stoploss order
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if trade.stop_loss > float(order['info']['stopPrice']):
|
||||
# we check if the update is neccesary
|
||||
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
|
||||
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat:
|
||||
# cancelling the current stoploss on exchange first
|
||||
logger.info('Trailing stoploss: cancelling current stoploss on exchange '
|
||||
'in order to add another one ...')
|
||||
if self.exchange.cancel_order(order['id'], trade.pair):
|
||||
# creating the new one
|
||||
stoploss_order_id = self.exchange.stoploss_limit(
|
||||
pair=trade.pair, amount=trade.amount,
|
||||
stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99
|
||||
)['id']
|
||||
trade.stoploss_order_id = str(stoploss_order_id)
|
||||
|
||||
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool:
|
||||
if self.edge:
|
||||
stoploss = self.edge.stoploss(trade.pair)
|
||||
|
@ -83,7 +83,7 @@ def check_migrate(engine) -> None:
|
||||
logger.debug(f'trying {table_back_name}')
|
||||
|
||||
# Check for latest column
|
||||
if not has_column(cols, 'stoploss_order_id'):
|
||||
if not has_column(cols, 'stoploss_last_update'):
|
||||
logger.info(f'Running database migration - backup available as {table_back_name}')
|
||||
|
||||
fee_open = get_column_def(cols, 'fee_open', 'fee')
|
||||
@ -93,6 +93,7 @@ def check_migrate(engine) -> None:
|
||||
stop_loss = get_column_def(cols, 'stop_loss', '0.0')
|
||||
initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
|
||||
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
|
||||
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
|
||||
max_rate = get_column_def(cols, 'max_rate', '0.0')
|
||||
sell_reason = get_column_def(cols, 'sell_reason', 'null')
|
||||
strategy = get_column_def(cols, 'strategy', 'null')
|
||||
@ -111,7 +112,8 @@ def check_migrate(engine) -> None:
|
||||
(id, exchange, pair, is_open, fee_open, fee_close, open_rate,
|
||||
open_rate_requested, close_rate, close_rate_requested, close_profit,
|
||||
stake_amount, amount, open_date, close_date, open_order_id,
|
||||
stop_loss, initial_stop_loss, stoploss_order_id, max_rate, sell_reason, strategy,
|
||||
stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update,
|
||||
max_rate, sell_reason, strategy,
|
||||
ticker_interval
|
||||
)
|
||||
select id, lower(exchange),
|
||||
@ -127,9 +129,9 @@ def check_migrate(engine) -> None:
|
||||
{close_rate_requested} close_rate_requested, close_profit,
|
||||
stake_amount, amount, open_date, close_date, open_order_id,
|
||||
{stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss,
|
||||
{stoploss_order_id} stoploss_order_id, {max_rate} max_rate,
|
||||
{sell_reason} sell_reason, {strategy} strategy,
|
||||
{ticker_interval} ticker_interval
|
||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
||||
{max_rate} max_rate, {sell_reason} sell_reason,
|
||||
{strategy} strategy, {ticker_interval} ticker_interval
|
||||
from {table_back_name}
|
||||
""")
|
||||
|
||||
@ -185,6 +187,8 @@ class Trade(_DECL_BASE):
|
||||
initial_stop_loss = Column(Float, nullable=True, default=0.0)
|
||||
# stoploss order id which is on exchange
|
||||
stoploss_order_id = Column(String, nullable=True, index=True)
|
||||
# last update time of the stoploss order on exchange
|
||||
stoploss_last_update = Column(DateTime, nullable=True)
|
||||
# absolute value of the highest reached price
|
||||
max_rate = Column(Float, nullable=True, default=0.0)
|
||||
sell_reason = Column(String, nullable=True)
|
||||
@ -218,11 +222,13 @@ class Trade(_DECL_BASE):
|
||||
logger.debug("assigning new stop loss")
|
||||
self.stop_loss = new_loss
|
||||
self.initial_stop_loss = new_loss
|
||||
self.stoploss_last_update = datetime.utcnow()
|
||||
|
||||
# evaluate if the stop loss needs to be updated
|
||||
else:
|
||||
if new_loss > self.stop_loss: # stop losses only walk up, never down!
|
||||
self.stop_loss = new_loss
|
||||
self.stoploss_last_update = datetime.utcnow()
|
||||
logger.debug("adjusted stop loss")
|
||||
else:
|
||||
logger.debug("keeping current stop loss")
|
||||
|
@ -80,7 +80,8 @@ class IStrategy(ABC):
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': False
|
||||
'stoploss_on_exchange': False,
|
||||
'stoploss_on_exchange_interval': 60,
|
||||
}
|
||||
|
||||
# Optional time in force
|
||||
@ -233,12 +234,9 @@ class IStrategy(ABC):
|
||||
current_rate = low or rate
|
||||
current_profit = trade.calc_profit_percent(current_rate)
|
||||
|
||||
if self.order_types.get('stoploss_on_exchange'):
|
||||
stoplossflag = SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
|
||||
else:
|
||||
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
|
||||
current_time=date, current_profit=current_profit,
|
||||
force_stoploss=force_stoploss)
|
||||
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
|
||||
current_time=date, current_profit=current_profit,
|
||||
force_stoploss=force_stoploss)
|
||||
|
||||
if stoplossflag.sell_flag:
|
||||
return stoplossflag
|
||||
@ -276,12 +274,13 @@ class IStrategy(ABC):
|
||||
"""
|
||||
|
||||
trailing_stop = self.config.get('trailing_stop', False)
|
||||
|
||||
trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss
|
||||
else self.stoploss, initial=True)
|
||||
|
||||
# evaluate if the stoploss was hit
|
||||
if self.stoploss is not None and trade.stop_loss >= current_rate:
|
||||
# evaluate if the stoploss was hit if stoploss is not on exchange
|
||||
if ((self.stoploss is not None) and
|
||||
(trade.stop_loss >= current_rate) and
|
||||
(not self.order_types.get('stoploss_on_exchange'))):
|
||||
selltype = SellType.STOP_LOSS
|
||||
# If Trailing stop (and max-rate did move above open rate)
|
||||
if trailing_stop and trade.open_rate != trade.max_rate:
|
||||
@ -301,7 +300,8 @@ class IStrategy(ABC):
|
||||
|
||||
# check if we have a special stop loss for positive condition
|
||||
# and if profit is positive
|
||||
stop_loss_value = self.stoploss
|
||||
stop_loss_value = force_stoploss if force_stoploss else 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:
|
||||
|
@ -1014,6 +1014,211 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
assert trade.is_open is False
|
||||
|
||||
|
||||
def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
|
||||
markets, limit_buy_order, limit_sell_order) -> None:
|
||||
# When trailing stoploss is set
|
||||
stoploss_limit = MagicMock(return_value={'id': 13434334})
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock(return_value={
|
||||
'bid': 0.00001172,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
get_markets=markets,
|
||||
stoploss_limit=stoploss_limit
|
||||
)
|
||||
|
||||
# enabling TSL
|
||||
default_conf['trailing_stop'] = True
|
||||
|
||||
# disabling ROI
|
||||
default_conf['minimal_roi']['0'] = 999999999
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
# enabling stoploss on exchange
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
|
||||
# setting stoploss
|
||||
freqtrade.strategy.stoploss = -0.05
|
||||
|
||||
# setting stoploss_on_exchange_interval to 60 seconds
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
freqtrade.create_trade()
|
||||
trade = Trade.query.first()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
|
||||
stoploss_order_hanging = MagicMock(return_value={
|
||||
'id': 100,
|
||||
'status': 'open',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2,
|
||||
'info': {
|
||||
'stopPrice': '0.000011134'
|
||||
}
|
||||
})
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging)
|
||||
|
||||
# stoploss initially at 5%
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
# price jumped 2x
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={
|
||||
'bid': 0.00002344,
|
||||
'ask': 0.00002346,
|
||||
'last': 0.00002344
|
||||
}))
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
stoploss_order_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock)
|
||||
|
||||
# stoploss should not be updated as the interval is 60 seconds
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
cancel_order_mock.assert_not_called()
|
||||
stoploss_order_mock.assert_not_called()
|
||||
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert trade.stop_loss == 0.00002344 * 0.95
|
||||
|
||||
# setting stoploss_on_exchange_interval to 0 seconds
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
cancel_order_mock.assert_called_once_with(100, 'ETH/BTC')
|
||||
stoploss_order_mock.assert_called_once_with(amount=85.25149190110828,
|
||||
pair='ETH/BTC',
|
||||
rate=0.00002344 * 0.95 * 0.99,
|
||||
stop_price=0.00002344 * 0.95)
|
||||
|
||||
|
||||
def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
||||
markets, limit_buy_order, limit_sell_order) -> None:
|
||||
|
||||
# When trailing stoploss is set
|
||||
stoploss_limit = MagicMock(return_value={'id': 13434334})
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_edge(mocker)
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock(return_value={
|
||||
'bid': 0.00001172,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
get_markets=markets,
|
||||
stoploss_limit=stoploss_limit
|
||||
)
|
||||
|
||||
# enabling TSL
|
||||
edge_conf['trailing_stop'] = True
|
||||
edge_conf['trailing_stop_positive'] = 0.01
|
||||
edge_conf['trailing_stop_positive_offset'] = 0.011
|
||||
|
||||
# disabling ROI
|
||||
edge_conf['minimal_roi']['0'] = 999999999
|
||||
|
||||
freqtrade = FreqtradeBot(edge_conf)
|
||||
|
||||
# enabling stoploss on exchange
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
|
||||
# setting stoploss
|
||||
freqtrade.strategy.stoploss = -0.02
|
||||
|
||||
# setting stoploss_on_exchange_interval to 0 second
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist)
|
||||
|
||||
freqtrade.create_trade()
|
||||
trade = Trade.query.first()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
|
||||
stoploss_order_hanging = MagicMock(return_value={
|
||||
'id': 100,
|
||||
'status': 'open',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2,
|
||||
'info': {
|
||||
'stopPrice': '0.000009384'
|
||||
}
|
||||
})
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hanging)
|
||||
|
||||
# stoploss initially at 20% as edge dictated it.
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.stop_loss == 0.000009384
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
stoploss_order_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order_mock)
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_order_mock)
|
||||
|
||||
# price goes down 5%
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={
|
||||
'bid': 0.00001172 * 0.95,
|
||||
'ask': 0.00001173 * 0.95,
|
||||
'last': 0.00001172 * 0.95
|
||||
}))
|
||||
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
# stoploss should remain the same
|
||||
assert trade.stop_loss == 0.000009384
|
||||
|
||||
# stoploss on exchange should not be canceled
|
||||
cancel_order_mock.assert_not_called()
|
||||
|
||||
# price jumped 2x
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker', MagicMock(return_value={
|
||||
'bid': 0.00002344,
|
||||
'ask': 0.00002346,
|
||||
'last': 0.00002344
|
||||
}))
|
||||
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
|
||||
# stoploss should be set to 1% as trailing is on
|
||||
assert trade.stop_loss == 0.00002344 * 0.99
|
||||
cancel_order_mock.assert_called_once_with(100, 'NEO/BTC')
|
||||
stoploss_order_mock.assert_called_once_with(amount=2131074.168797954,
|
||||
pair='NEO/BTC',
|
||||
rate=0.00002344 * 0.99 * 0.99,
|
||||
stop_price=0.00002344 * 0.99)
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy(mocker, default_conf) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
@ -516,6 +516,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
assert trade.strategy is None
|
||||
assert trade.ticker_interval is None
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.stoploss_last_update is None
|
||||
assert log_has("trying trades_bak1", caplog.record_tuples)
|
||||
assert log_has("trying trades_bak2", caplog.record_tuples)
|
||||
assert log_has("Running database migration - backup available as trades_bak2",
|
||||
|
Loading…
Reference in New Issue
Block a user