diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 8a3e28379..45102359d 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -32,6 +32,13 @@ class Binance(Exchange): return super().get_order_book(pair, limit) + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ creates a stoploss limit order. diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index bef92750c..a8df4c1bb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -282,8 +282,8 @@ class Exchange: quote_currencies = self.get_quote_currencies() if stake_currency not in quote_currencies: raise OperationalException( - f"{stake_currency} is not available as stake on {self.name}. " - f"Available currencies are: {', '.join(quote_currencies)}") + f"{stake_currency} is not available as stake on {self.name}. " + f"Available currencies are: {', '.join(quote_currencies)}") def validate_pairs(self, pairs: List[str]) -> None: """ @@ -460,7 +460,7 @@ class Exchange: "status": "closed", "filled": closed_order["amount"], "remaining": 0 - }) + }) if closed_order["type"] in ["stop_loss_limit"]: closed_order["info"].update({"stopPrice": closed_order["price"]}) self._dry_run_open_orders[closed_order["id"]] = closed_order @@ -519,6 +519,13 @@ class Exchange: return self.create_order(pair, ordertype, 'sell', amount, rate, params) + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + raise OperationalException(f"stoploss is not implemented for {self.name}.") + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ creates a stoploss order. diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 88c414772..243f1a6d6 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -51,6 +51,13 @@ class Kraken(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['type'] == 'stop-loss' and stop_loss > float(order['price']) + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ Creates a stoploss market order. diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a4b0ab806..fa9a8424a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -718,8 +718,7 @@ class FreqtradeBot: :param order: Current on exchange stoploss order :return: None """ - - if trade.stop_loss > float(order['info']['stopPrice']): + if self.exchange.stoploss_adjust(trade.stop_loss, order): # 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: diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index a1b24913e..e4599dcd7 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -92,3 +92,17 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): assert order['type'] == order_type assert order['price'] == 220 assert order['amount'] == 1 + + +def test_stoploss_adjust_binance(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='binance') + order = { + 'type': 'stop_loss_limit', + 'price': 1500, + 'info': {'stopPrice': 1500}, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case + order['type'] = 'stop_loss' + assert not exchange.stoploss_adjust(1501, order) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 680e69764..3a664a9ec 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1763,6 +1763,9 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): + exchange.stoploss_adjust(1, {}) + def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 241d15772..d63dd66cc 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -236,3 +236,16 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker): assert order['type'] == order_type assert order['price'] == 220 assert order['amount'] == 1 + + +def test_stoploss_adjust_kraken(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='kraken') + order = { + 'type': 'stop-loss', + 'price': 1500, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case ... + order['type'] = 'stop_loss_limit' + assert not exchange.stoploss_adjust(1501, order)