Repository: freqtrade/freqtrade

Issue:
stoploss on exchange issue
I got again stoploss on exchange issues

thats the part of my config:

    "order_types": {
    "buy": "limit",
    "sell": "limit",
    "stoploss": "market",

The part of my strategy file:

    # This attribute will be overridden if the config file contains "stoploss"
    stoploss = -0.15

    # Optimal ticker interval for the strategy
    ticker_interval = '5m'

    # trailing stoploss
    trailing_stop = True   
  
    trailing_stop_positive = 0.005
    trailing_stop_positive_offset = 0.015
    trailing_only_offset_is_reached = False 


and the log from freqtrade - any idea where the problem could be? 

2019-04-02 10:34:30,775 - freqtrade.exchange - WARNING - Giving up retrying: cancel_order()
2019-04-02 10:34:30,775 - freqtrade.freqtradebot - WARNING - Unable to sell trade: Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}
2019-04-02 10:34:31,268 - freqtrade.freqtradebot - INFO - Trailing stoploss: cancelling current stoploss on exchange in order to add another one ...
2019-04-02 10:34:31,771 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:31,771 - freqtrade.exchange - WARNING - retrying cancel_order() still for 3 times
2019-04-02 10:34:32,272 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:32,273 - freqtrade.exchange - WARNING - retrying cancel_order() still for 2 times
2019-04-02 10:34:32,762 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:32,762 - freqtrade.exchange - WARNING - retrying cancel_order() still for 1 times
2019-04-02 10:34:33,268 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:33,268 - freqtrade.exchange - WARNING - retrying cancel_order() still for 0 times
2019-04-02 10:34:33,776 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:33,776 - freqtrade.exchange - WARNING - Giving up retrying: cancel_order()
2019-04-02 10:34:33,777 - freqtrade.freqtradebot - WARNING - Unable to sell trade: Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}
2019-04-02 10:34:33,877 - freqtrade.freqtradebot - INFO - Found no buy signals for whitelisted currencies. Trying again..
2019-04-02 10:34:34,771 - freqtrade.freqtradebot - INFO - Trailing stoploss: cancelling current stoploss on exchange in order to add another one ...
2019-04-02 10:34:35,275 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:35,276 - freqtrade.exchange - WARNING - retrying cancel_order() still for 3 times
2019-04-02 10:34:35,775 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:35,775 - freqtrade.exchange - WARNING - retrying cancel_order() still for 2 times
2019-04-02 10:34:36,276 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:36,277 - freqtrade.exchange - WARNING - retrying cancel_order() still for 1 times
2019-04-02 10:34:36,773 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:36,773 - freqtrade.exchange - WARNING - retrying cancel_order() still for 0 times
2019-04-02 10:34:37,274 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:37,274 - freqtrade.exchange - WARNING - Giving up retrying: cancel_order()
2019-04-02 10:34:37,275 - freqtrade.freqtradebot - WARNING - Unable to sell trade: Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}
2019-04-02 10:34:37,778 - freqtrade.freqtradebot - INFO - Trailing stoploss: cancelling current stoploss on exchange in order to add another one ...
2019-04-02 10:34:38,277 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:38,277 - freqtrade.exchange - WARNING - retrying cancel_order() still for 3 times
2019-04-02 10:34:38,773 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:38,773 - freqtrade.exchange - WARNING - retrying cancel_order() still for 2 times
2019-04-02 10:34:39,274 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:39,274 - freqtrade.exchange - WARNING - retrying cancel_order() still for 1 times
2019-04-02 10:34:39,777 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:39,778 - freqtrade.exchange - WARNING - retrying cancel_order() still for 0 times
2019-04-02 10:34:40,283 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:40,283 - freqtrade.exchange - WARNING - Giving up retrying: cancel_order()
2019-04-02 10:34:40,283 - freqtrade.freqtradebot - WARNING - Unable to sell trade: Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}
2019-04-02 10:34:40,403 - freqtrade.freqtradebot - INFO - Found no buy signals for whitelisted currencies. Trying again..
2019-04-02 10:34:41,282 - freqtrade.freqtradebot - INFO - Trailing stoploss: cancelling current stoploss on exchange in order to add another one ...
2019-04-02 10:34:41,784 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:41,784 - freqtrade.exchange - WARNING - retrying cancel_order() still for 3 times
2019-04-02 10:34:42,281 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:42,281 - freqtrade.exchange - WARNING - retrying cancel_order() still for 2 times
2019-04-02 10:34:42,785 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:42,785 - freqtrade.exchange - WARNING - retrying cancel_order() still for 1 times
2019-04-02 10:34:43,284 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:43,285 - freqtrade.exchange - WARNING - retrying cancel_order() still for 0 times
2019-04-02 10:34:43,785 - freqtrade.exchange - WARNING - cancel_order() returned exception: "Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}"
2019-04-02 10:34:43,786 - freqtrade.exchange - WARNING - Giving up retrying: cancel_order()
2019-04-02 10:34:43,786 - freqtrade.freqtradebot - WARNING - Unable to sell trade: Could not cancel order. Message: binance {"code":-2011,"msg":"Unknown order sent."}







Discussion:
are you able to fetch the stoploss_order_id column in the DB ? just take a look if it contains an ID or it is Null.
stoploss order id column is NULL ... I had changed to stoploss off exchange after facing this problem ... reloaded the config and restarted the bot. The bot executed the trailing stop loss then .. I guess thats why it is NULL now.

I have seen thtat the buy order was only filled partially then the bot had problems to adjust the trailing stop loss on exchange - could this be the reason? I had a similar problem recently. In my case the stoploss on exchange works very instable...
Ok, I think I know what the problem is. the order is fulfilled and binance actually returns it doesn't recognize the order instead saying it is already fulfilled.
@xmatthias 
cancel_order in this case should not retry, but CCXT returns DependencyException so it keeps retrying: 

```python
@retrier
    def cancel_order(self, order_id: str, pair: str) -> None:
        if self._config['dry_run']:
            return

        try:
            return self._api.cancel_order(order_id, pair)
        except ccxt.InvalidOrder as e:
            raise DependencyException(
                f'Could not cancel order. Message: {e}')
        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
            raise TemporaryError(
                f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
        except ccxt.BaseError as e:
            raise OperationalException(e)
```

Any idea how we can work around it? :/

**EDIT:** oh no, I think its our fault ... we don't have InvalidOrder exception ...

Resolving patch:
diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py
--- a/freqtrade/__init__.py
+++ b/freqtrade/__init__.py
@@ -17,6 +17,14 @@ class OperationalException(BaseException):
     """
 
 
+class InvalidOrderException(BaseException):
+    """
+    This is returned when the order is not valid. Example:
+    If stoploss on exchange order is hit, then trying to cancel the order
+    should return this exception.
+    """
+
+
 class TemporaryError(BaseException):
     """
     Temporary network or exchange related error.
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -13,7 +13,8 @@
 import ccxt.async_support as ccxt_async
 from pandas import DataFrame
 
-from freqtrade import constants, DependencyException, OperationalException, TemporaryError
+from freqtrade import (constants, DependencyException, OperationalException,
+                       TemporaryError, InvalidOrderException)
 from freqtrade.data.converter import parse_ticker_dataframe
 
 logger = logging.getLogger(__name__)
@@ -607,7 +608,7 @@ def cancel_order(self, order_id: str, pair: str) -> None:
         try:
             return self._api.cancel_order(order_id, pair)
         except ccxt.InvalidOrder as e:
-            raise DependencyException(
+            raise InvalidOrderException(
                 f'Could not cancel order. Message: {e}')
         except (ccxt.NetworkError, ccxt.ExchangeError) as e:
             raise TemporaryError(
@@ -623,8 +624,8 @@ def get_order(self, order_id: str, pair: str) -> Dict:
         try:
             return self._api.fetch_order(order_id, pair)
         except ccxt.InvalidOrder as e:
-            raise DependencyException(
-                f'Could not get order. Message: {e}')
+            raise InvalidOrderException(
+                f'Tried to get an invalid order (id: {order_id}). Message: {e}')
         except (ccxt.NetworkError, ccxt.ExchangeError) as e:
             raise TemporaryError(
                 f'Could not get order due to {e.__class__.__name__}. Message: {e}')
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -11,7 +11,7 @@
 import arrow
 from requests.exceptions import RequestException
 
-from freqtrade import (DependencyException, OperationalException,
+from freqtrade import (DependencyException, OperationalException, InvalidOrderException,
                        __version__, constants, persistence)
 from freqtrade.data.converter import order_book_to_dataframe
 from freqtrade.data.dataprovider import DataProvider
@@ -590,46 +590,74 @@ def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
         is enabled.
         """
 
-        result = False
+        logger.debug('Handling stoploss on exchange %s ...', trade)
+
+        stoploss_order = None
+
         try:
-            # If trade is open and the buy order is fulfilled but there is no stoploss,
-            # then we add a stoploss on exchange
-            if not trade.open_order_id and not trade.stoploss_order_id:
-                if self.edge:
-                    stoploss = self.edge.stoploss(pair=trade.pair)
-                else:
-                    stoploss = self.strategy.stoploss
+            # First we check if there is already a stoploss on exchange
+            stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \
+                if trade.stoploss_order_id else None
+        except InvalidOrderException as exception:
+            logger.warning('Unable to fetch stoploss order: %s', exception)
+
+        # If trade open order id does not exist: buy order is fulfilled
+        buy_order_fulfilled = not trade.open_order_id
+
+        # Limit price threshold: As limit price should always be below price
+        limit_price_pct = 0.99
+
+        # If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
+        if (buy_order_fulfilled and not stoploss_order):
+            if self.edge:
+                stoploss = self.edge.stoploss(pair=trade.pair)
+            else:
+                stoploss = self.strategy.stoploss
 
-                stop_price = trade.open_rate * (1 + stoploss)
+            stop_price = trade.open_rate * (1 + stoploss)
 
-                # limit price should be less than stop price.
-                # 0.99 is arbitrary here.
-                limit_price = stop_price * 0.99
+            # limit price should be less than stop price.
+            limit_price = stop_price * limit_price_pct
 
+            try:
                 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()
+                return False
 
-            # Or the trade open and there is already a stoploss on exchange.
-            # so we check if it is hit ...
-            elif trade.stoploss_order_id:
-                logger.debug('Handling stoploss on exchange %s ...', trade)
-                order = self.exchange.get_order(trade.stoploss_order_id, trade.pair)
-                if order['status'] == 'closed':
-                    trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
-                    trade.update(order)
-                    self.notify_sell(trade)
-                    result = True
-                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)
-        except DependencyException as exception:
-            logger.warning('Unable to create stoploss order: %s', exception)
-        return result
+            except DependencyException as exception:
+                logger.warning('Unable to place a stoploss order on exchange: %s', exception)
+
+        # If stoploss order is canceled for some reason we add it
+        if stoploss_order and stoploss_order['status'] == 'canceled':
+            try:
+                stoploss_order_id = self.exchange.stoploss_limit(
+                    pair=trade.pair, amount=trade.amount,
+                    stop_price=trade.stop_loss, rate=trade.stop_loss * limit_price_pct
+                )['id']
+                trade.stoploss_order_id = str(stoploss_order_id)
+                return False
+            except DependencyException as exception:
+                logger.warning('Stoploss order was cancelled, '
+                               'but unable to recreate one: %s', exception)
+
+        # We check if stoploss order is fulfilled
+        if stoploss_order and stoploss_order['status'] == 'closed':
+            trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
+            trade.update(stoploss_order)
+            self.notify_sell(trade)
+            return True
+
+        # Finally we check if stoploss on exchange should be moved up because of trailing.
+        if stoploss_order and 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, stoploss_order)
+
+        return False
 
     def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
         """
@@ -645,8 +673,8 @@ def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
             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 ...')
+                logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
+                            'in order to add another one ...', order['id'])
                 if self.exchange.cancel_order(order['id'], trade.pair):
                     # creating the new one
                     stoploss_order_id = self.exchange.stoploss_limit(


Test patch:
diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py
--- a/freqtrade/tests/exchange/test_exchange.py
+++ b/freqtrade/tests/exchange/test_exchange.py
@@ -11,7 +11,8 @@
 import pytest
 from pandas import DataFrame
 
-from freqtrade import DependencyException, OperationalException, TemporaryError
+from freqtrade import (DependencyException, OperationalException,
+                       TemporaryError, InvalidOrderException)
 from freqtrade.exchange import Binance, Exchange, Kraken
 from freqtrade.exchange.exchange import API_RETRY_COUNT
 from freqtrade.resolvers.exchange_resolver import ExchangeResolver
@@ -1233,11 +1234,11 @@ def test_cancel_order(default_conf, mocker, exchange_name):
     exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
     assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
 
-    with pytest.raises(DependencyException):
+    with pytest.raises(InvalidOrderException):
         api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
         exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
         exchange.cancel_order(order_id='_', pair='TKN/BTC')
-    assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
+    assert api_mock.cancel_order.call_count == 1
 
     ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
                            "cancel_order", "cancel_order",
@@ -1260,11 +1261,11 @@ def test_get_order(default_conf, mocker, exchange_name):
     exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
     assert exchange.get_order('X', 'TKN/BTC') == 456
 
-    with pytest.raises(DependencyException):
+    with pytest.raises(InvalidOrderException):
         api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
         exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
         exchange.get_order(order_id='_', pair='TKN/BTC')
-    assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
+    assert api_mock.fetch_order.call_count == 1
 
     ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
                            'get_order', 'fetch_order',
diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py
--- a/freqtrade/tests/test_freqtradebot.py
+++ b/freqtrade/tests/test_freqtradebot.py
@@ -12,7 +12,7 @@
 import requests
 
 from freqtrade import (DependencyException, OperationalException,
-                       TemporaryError, constants)
+                       TemporaryError, InvalidOrderException, constants)
 from freqtrade.data.dataprovider import DataProvider
 from freqtrade.freqtradebot import FreqtradeBot
 from freqtrade.persistence import Trade
@@ -1036,7 +1036,21 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
     assert freqtrade.handle_stoploss_on_exchange(trade) is False
     assert trade.stoploss_order_id == 100
 
-    # Third case: when stoploss is set and it is hit
+    # Third case: when stoploss was set but it was canceled for some reason
+    # should set a stoploss immediately and return False
+    trade.is_open = True
+    trade.open_order_id = None
+    trade.stoploss_order_id = 100
+
+    canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
+    mocker.patch('freqtrade.exchange.Exchange.get_order', canceled_stoploss_order)
+    stoploss_limit.reset_mock()
+
+    assert freqtrade.handle_stoploss_on_exchange(trade) is False
+    assert stoploss_limit.call_count == 1
+    assert trade.stoploss_order_id == "13434334"
+
+    # Fourth case: when stoploss is set and it is hit
     # should unset stoploss_order_id and return true
     # as a trade actually happened
     freqtrade.create_trade()
@@ -1063,7 +1077,16 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
         side_effect=DependencyException()
     )
     freqtrade.handle_stoploss_on_exchange(trade)
-    assert log_has('Unable to create stoploss order: ', caplog.record_tuples)
+    assert log_has('Unable to place a stoploss order on exchange: ', caplog.record_tuples)
+
+    # Fifth case: get_order returns InvalidOrder
+    # It should try to add stoploss order
+    trade.stoploss_order_id = 100
+    stoploss_limit.reset_mock()
+    mocker.patch('freqtrade.exchange.Exchange.get_order', side_effect=InvalidOrderException())
+    mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit)
+    freqtrade.handle_stoploss_on_exchange(trade)
+    assert stoploss_limit.call_count == 1
 
 
 def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,