Repository: freqtrade/freqtrade

Issue:
Kraken: stoploss on exchange fails to be placed
Operating system: Debian 6.1.67-1
Python Version: Python 3.11.2
CCXT version: ccxt==4.1.98
Freqtrade Version: freqtrade 2023.12
  
Hi, I went live with my bot for the first time today and am getting the below error from Kraken. I have the same config running on Binance, no errors there. The problem has been addressed before, but no solution has been found.


2024-01-12 09:14:17,701 - freqtrade.exchange.common - WARNING - create_stoploss() returned exception: "Could not place sell order due to ExchangeError. Message: kraken {"error":["EGeneral:Invalid arguments:price"]}". Giving up.
2024-01-12 09:14:17,701 - freqtrade.freqtradebot - ERROR - Unable to place a stoploss order on exchange.
Traceback (most recent call last):
  File "/app/freqtrade/freqtrade/exchange/kraken.py", line 137, in create_stoploss
    order = self._api.create_order(symbol=pair, type=ordertype, side=side,
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/freqtrade/.venv/lib/python3.11/site-packages/ccxt/kraken.py", line 1328, in create_order
    response = self.privatePostAddOrder(self.extend(orderRequest[0], orderRequest[1]))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/freqtrade/.venv/lib/python3.11/site-packages/ccxt/base/types.py", line 34, in unbound_method
    return _self.request(self.path, self.api, self.method, params, config=self.config)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/freqtrade/.venv/lib/python3.11/site-packages/ccxt/base/exchange.py", line 3332, in request
    return self.fetch2(path, api, method, params, headers, body, config)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/freqtrade/.venv/lib/python3.11/site-packages/ccxt/base/exchange.py", line 3329, in fetch2
    return self.fetch(request['url'], request['method'], request['headers'], request['body'])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/freqtrade/.venv/lib/python3.11/site-packages/ccxt/base/exchange.py", line 665, in fetch
    self.handle_errors(http_status_code, http_status_text, url, method, headers, http_response, json_response, request_headers, request_body)
  File "/app/freqtrade/.venv/lib/python3.11/site-packages/ccxt/kraken.py", line 2661, in handle_errors
    raise ExchangeError(message)
ccxt.base.errors.ExchangeError: kraken {"error":["EGeneral:Invalid arguments:price"]}

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/app/freqtrade/freqtrade/freqtradebot.py", line 1188, in create_stoploss_order
    stoploss_order = self.exchange.create_stoploss(
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/freqtrade/freqtrade/exchange/common.py", line 182, in wrapper
    raise ex
  File "/app/freqtrade/freqtrade/exchange/common.py", line 167, in wrapper
    return f(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^
  File "/app/freqtrade/freqtrade/exchange/kraken.py", line 156, in create_stoploss
    raise TemporaryError(
freqtrade.exceptions.TemporaryError: Could not place sell order due to ExchangeError. Message: kraken {"error":["EGeneral:Invalid arguments:price"]}

My configuration looks like this:
```
    "stake_currency": "EUR",
    "stake_amount": "unlimited",
    "tradable_balance_ratio": 0.50,
    "fiat_display_currency": "EUR",
    "cancel_open_orders_on_exit": false,
    "trading_mode": "spot",
    "margin_mode": "",
    "download_trades": true,

    "order_types": {
        "entry": "limit",
        "exit": "market",
        "emergency_exit": "market",
        "force_exit": "market",
        "force_entry": "market",
        "stoploss": "market",
        "stoploss_on_exchange": true,
        "stoploss_on_exchange_interval": 60,
        "stoploss_on_exchange_limit_ratio": 0.99
    },
...
    "exit_pricing": {
        "price_side": "other",
        "use_order_book": true,
        "order_book_top": 1
        },
```
What am I doing wrong? Thank you in advance for your answer.

VerDoe





Discussion:
well kraken's stoploss (through ccxt) long had the "sickness" of having the prices on stoploss orders switched, which requires special (odd) handling for stop orders on kraken. If ccxt fixed this, it MIGHT cause other issues (we can't test for this reliably in CI as it requies exchange keys).

you might wanna test with `"stoploss": "limit",`, too - as that's slightly different handling - and might be more reliable.
Hello Matthias, that doesn't work either. I get the same error. I have now disabled stoploss_on_exchange. In the latest version of ccxt some things have been done regarding Kraken. Unfortunately I don't know if it worked before, as I have only recently started using Freqtrade. Thank you for your help and big praise for your work.
it definitely did - as i did use kraken in the past actively (though it's been probably 1-2 years ago).

I'll have a look sometime this weekend i hope.

Seems like a ccxt bug - which i currently fail to get around ... 
opened an issue there ... https://github.com/ccxt/ccxt/issues/20814

Resolving patch:
diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py
--- a/freqtrade/exchange/kraken.py
+++ b/freqtrade/exchange/kraken.py
@@ -8,11 +8,9 @@
 
 from freqtrade.constants import BuySell
 from freqtrade.enums import MarginMode, TradingMode
-from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
-                                  OperationalException, TemporaryError)
+from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
 from freqtrade.exchange import Exchange
 from freqtrade.exchange.common import retrier
-from freqtrade.exchange.exchange_utils import ROUND_DOWN, ROUND_UP
 from freqtrade.exchange.types import Tickers
 
 
@@ -24,8 +22,9 @@ class Kraken(Exchange):
     _params: Dict = {"trading_agreement": "agree"}
     _ft_has: Dict = {
         "stoploss_on_exchange": True,
-        "stop_price_param": "stopPrice",
-        "stop_price_prop": "stopPrice",
+        "stop_price_param": "stopLossPrice",
+        "stop_price_prop": "stopLossPrice",
+        "stoploss_order_types": {"limit": "limit", "market": "market"},
         "order_time_in_force": ["GTC", "IOC", "PO"],
         "ohlcv_candle_limit": 720,
         "ohlcv_has_history": False,
@@ -90,75 +89,6 @@ def get_balances(self) -> dict:
         except ccxt.BaseError as e:
             raise OperationalException(e) from e
 
-    def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
-        """
-        Verify stop_loss against stoploss-order value (limit or price)
-        Returns True if adjustment is necessary.
-        """
-        return (order['type'] in ('stop-loss', 'stop-loss-limit') and (
-                (side == "sell" and stop_loss > float(order['price'])) or
-                (side == "buy" and stop_loss < float(order['price']))
-                ))
-
-    @retrier(retries=0)
-    def create_stoploss(self, pair: str, amount: float, stop_price: float,
-                        order_types: Dict, side: BuySell, leverage: float) -> Dict:
-        """
-        Creates a stoploss market order.
-        Stoploss market orders is the only stoploss type supported by kraken.
-        TODO: investigate if this can be combined with generic implementation
-              (careful, prices are reversed)
-        """
-        params = self._params.copy()
-        if self.trading_mode == TradingMode.FUTURES:
-            params.update({'reduceOnly': True})
-
-        round_mode = ROUND_DOWN if side == 'buy' else ROUND_UP
-        if order_types.get('stoploss', 'market') == 'limit':
-            ordertype = "stop-loss-limit"
-            limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
-            if side == "sell":
-                limit_rate = stop_price * limit_price_pct
-            else:
-                limit_rate = stop_price * (2 - limit_price_pct)
-            params['price2'] = self.price_to_precision(pair, limit_rate, rounding_mode=round_mode)
-        else:
-            ordertype = "stop-loss"
-
-        stop_price = self.price_to_precision(pair, stop_price, rounding_mode=round_mode)
-
-        if self._config['dry_run']:
-            dry_order = self.create_dry_run_order(
-                pair, ordertype, side, amount, stop_price, leverage, stop_loss=True)
-            return dry_order
-
-        try:
-            amount = self.amount_to_precision(pair, amount)
-
-            order = self._api.create_order(symbol=pair, type=ordertype, side=side,
-                                           amount=amount, price=stop_price, params=params)
-            self._log_exchange_response('create_stoploss_order', order)
-            logger.info('stoploss order added for %s. '
-                        'stop price: %s.', pair, stop_price)
-            return order
-        except ccxt.InsufficientFunds as e:
-            raise InsufficientFundsError(
-                f'Insufficient funds to create {ordertype} {side} order on market {pair}. '
-                f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
-                f'Message: {e}') from e
-        except ccxt.InvalidOrder as e:
-            raise InvalidOrderException(
-                f'Could not create {ordertype} {side} order on market {pair}. '
-                f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
-                f'Message: {e}') from e
-        except ccxt.DDoSProtection as e:
-            raise DDosProtection(e) from e
-        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
-            raise TemporaryError(
-                f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e
-        except ccxt.BaseError as e:
-            raise OperationalException(e) from e
-
     def _set_leverage(
         self,
         leverage: float,
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -70,7 +70,7 @@
     ],
     install_requires=[
         # from requirements.txt
-        'ccxt>=4.0.0',
+        'ccxt>=4.2.15',
         'SQLAlchemy>=2.0.6',
         'python-telegram-bot>=20.1',
         'arrow>=1.0.0',


Test patch:
diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py
--- a/tests/exchange/test_kraken.py
+++ b/tests/exchange/test_kraken.py
@@ -183,19 +183,17 @@ def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adj
     assert 'info' in order
     assert order['id'] == order_id
     assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
-    if ordertype == 'limit':
-        assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_LIMIT_ORDERTYPE
-        assert api_mock.create_order.call_args_list[0][1]['params'] == {
-            'trading_agreement': 'agree',
-            'price2': adjustedprice
-        }
-    else:
-        assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
-        assert api_mock.create_order.call_args_list[0][1]['params'] == {
-            'trading_agreement': 'agree'}
+    assert api_mock.create_order.call_args_list[0][1]['type'] == ordertype
+    assert api_mock.create_order.call_args_list[0][1]['params'] == {
+        'trading_agreement': 'agree',
+        'stopLossPrice': 220
+    }
     assert api_mock.create_order.call_args_list[0][1]['side'] == side
     assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
-    assert api_mock.create_order.call_args_list[0][1]['price'] == 220
+    if ordertype == 'limit':
+        assert api_mock.create_order.call_args_list[0][1]['price'] == adjustedprice
+    else:
+        assert api_mock.create_order.call_args_list[0][1]['price'] is None
 
     # test exception handling
     with pytest.raises(DependencyException):
@@ -253,7 +251,7 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
     assert 'info' in order
     assert 'type' in order
 
-    assert order['type'] == STOPLOSS_ORDERTYPE
+    assert order['type'] == 'market'
     assert order['price'] == 220
     assert order['amount'] == 1
 
@@ -265,11 +263,11 @@ def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
 def test_stoploss_adjust_kraken(mocker, default_conf, sl1, sl2, sl3, side):
     exchange = get_patched_exchange(mocker, default_conf, id='kraken')
     order = {
-        'type': STOPLOSS_ORDERTYPE,
-        'price': 1500,
+        'type': 'market',
+        'stopLossPrice': 1500,
     }
     assert exchange.stoploss_adjust(sl1, order, side=side)
     assert not exchange.stoploss_adjust(sl2, order, side=side)
-    # Test with invalid order case ...
-    order['type'] = 'stop_loss_limit'
-    assert not exchange.stoploss_adjust(sl3, order, side=side)
+    # diff. order type ...
+    order['type'] = 'limit'
+    assert exchange.stoploss_adjust(sl3, order, side=side)