Repository: freqtrade/freqtrade

Issue:
Kraken: EUR zero balance, BTC unknown format 
## Step 1: Have you search for this issue before posting it?
Yes, I am done my research. I found what I believe was a similar issue that was resolved but has not resurfaced --> #1840  


## Step 2: Describe your environment

  * Operating system: Ubuntu 19.04
  * Python Version: Python 3.7.3 
  * CCXT version: ccxt==1.18.1085 
  * Branch: Master
  * Last Commit ID: a7e45c5a732c60e2815bfd7bd3f2cb4cb24faf56 

 
  
## Step 3: Describe the problem:
I am wanting to use Kraken as my exchange. when using BTC I receive the below error:

 **File "/home/sgarc90/freqtrade/freqtrade/rpc/telegram.py", line 52, in wrapper
    return command_handler(self, *args, **kwargs)
  File "/home/sgarc90/freqtrade/freqtrade/rpc/telegram.py", line 336, in _balance
    "\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)**
--------------------------
When I change to EUR. a zero balance is returned.

WARNING - Unable to create trade: Available balance(0 EUR) is lower than stake amount(19 EUR)
*Explain the problem you have encountered*

### Steps to reproduce:
once you start up freqtrade using Kraken. from within telegram send the balance button and watch the screen is a short time the telegram request will error out.

I just happened to see the EUR error appear on the screen when freqtrade tried to place an order
 
  



Discussion:
The problem here is the return from ccxt - which does only contain "total" - but not "available", nor "free" per pair.

i know this worked before since I tested kraken in the past. 

Maybe it's simply a bug in ccxt, or  the exchange did change how / what they return.

In the latter case, this will land kraken on the blocked list, since freqtrade cannot operate if it does not know how much free balance is available.


Related ccxt issue: https://github.com/ccxt/ccxt/issues/5776
according to the answer from ccxt, the API does not return free balance - so ccxt can't either (which is understandable).

However, without knowing free balance, we cannot trade kraken - so i've added Kraken to the list of disabled exchanges for now (until we have a workaround for this ...).

Yeah, I've seen that issue in ccxt repo...

Thank you, all, for running this down so quickly.  Unless there are objections, I am going to consider this matter closed and close this thread.

Resolving patch:
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -30,9 +30,6 @@
     "bitmex": "Various reasons",
     "bitstamp": "Does not provide history. "
                 "Details in https://github.com/freqtrade/freqtrade/issues/1983",
-    "kraken": "TEMPORARY: Balance does not report free balance, so freqtrade will not know "
-              "if enough balance is available."
-              "Details in https://github.com/freqtrade/freqtrade/issues/1687#issuecomment-528509266"
     }
 
 
diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py
--- a/freqtrade/exchange/kraken.py
+++ b/freqtrade/exchange/kraken.py
@@ -2,7 +2,11 @@
 import logging
 from typing import Dict
 
+import ccxt
+
+from freqtrade import OperationalException, TemporaryError
 from freqtrade.exchange import Exchange
+from freqtrade.exchange.exchange import retrier
 
 logger = logging.getLogger(__name__)
 
@@ -10,3 +14,33 @@
 class Kraken(Exchange):
 
     _params: Dict = {"trading_agreement": "agree"}
+
+    @retrier
+    def get_balances(self) -> dict:
+        if self._config['dry_run']:
+            return {}
+
+        try:
+            balances = self._api.fetch_balance()
+            # Remove additional info from ccxt results
+            balances.pop("info", None)
+            balances.pop("free", None)
+            balances.pop("total", None)
+            balances.pop("used", None)
+
+            orders = self._api.fetch_open_orders()
+            order_list = [(x["symbol"].split("/")[0 if x["side"] == "sell" else 1],
+                           x["remaining"],
+                           # Don't remove the below comment, this can be important for debuggung
+                           # x["side"], x["amount"],
+                           ) for x in orders]
+            for bal in balances:
+                balances[bal]['used'] = sum(order[1] for order in order_list if order[0] == bal)
+                balances[bal]['free'] = balances[bal]['total'] - balances[bal]['used']
+
+            return balances
+        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
+            raise TemporaryError(
+                f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
+        except ccxt.BaseError as e:
+            raise OperationalException(e) from e


Test patch:
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -867,7 +867,7 @@ def test_get_balance_dry_run(default_conf, mocker):
 @pytest.mark.parametrize("exchange_name", EXCHANGES)
 def test_get_balance_prod(default_conf, mocker, exchange_name):
     api_mock = MagicMock()
-    api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}})
+    api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4, 'total': 123.4}})
     default_conf['dry_run'] = False
 
     exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
@@ -883,6 +883,7 @@ def test_get_balance_prod(default_conf, mocker, exchange_name):
     with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'):
         exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
         mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={}))
+        mocker.patch('freqtrade.exchange.Kraken.get_balances', MagicMock(return_value={}))
         exchange.get_balance(currency='BTC')
 
 
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
@@ -4,6 +4,7 @@
 from unittest.mock import MagicMock
 
 from tests.conftest import get_patched_exchange
+from tests.exchange.test_exchange import ccxt_exceptionhandlers
 
 
 def test_buy_kraken_trading_agreement(default_conf, mocker):
@@ -67,3 +68,84 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
     assert api_mock.create_order.call_args[0][3] == 1
     assert api_mock.create_order.call_args[0][4] is None
     assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'}
+
+
+def test_get_balances_prod(default_conf, mocker):
+    balance_item = {
+        'free': None,
+        'total': 10.0,
+        'used': 0.0
+    }
+
+    api_mock = MagicMock()
+    api_mock.fetch_balance = MagicMock(return_value={
+        '1ST': balance_item.copy(),
+        '2ST': balance_item.copy(),
+        '3ST': balance_item.copy(),
+        '4ST': balance_item.copy(),
+    })
+    kraken_open_orders = [{'symbol': '1ST/EUR',
+                           'type': 'limit',
+                           'side': 'sell',
+                           'price': 20,
+                           'cost': 0.0,
+                           'amount': 1.0,
+                           'filled': 0.0,
+                           'average': 0.0,
+                           'remaining': 1.0,
+                           },
+                          {'status': 'open',
+                           'symbol': '2ST/EUR',
+                           'type': 'limit',
+                           'side': 'sell',
+                           'price': 20.0,
+                           'cost': 0.0,
+                           'amount': 2.0,
+                           'filled': 0.0,
+                           'average': 0.0,
+                           'remaining': 2.0,
+                           },
+                          {'status': 'open',
+                           'symbol': '2ST/USD',
+                           'type': 'limit',
+                           'side': 'sell',
+                           'price': 20.0,
+                           'cost': 0.0,
+                           'amount': 2.0,
+                           'filled': 0.0,
+                           'average': 0.0,
+                           'remaining': 2.0,
+                           },
+                          {'status': 'open',
+                           'symbol': 'BTC/3ST',
+                           'type': 'limit',
+                           'side': 'buy',
+                           'price': 20,
+                           'cost': 0.0,
+                           'amount': 3.0,
+                           'filled': 0.0,
+                           'average': 0.0,
+                           'remaining': 3.0,
+                           }]
+    api_mock.fetch_open_orders = MagicMock(return_value=kraken_open_orders)
+    default_conf['dry_run'] = False
+    exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
+    balances = exchange.get_balances()
+    assert len(balances) == 4
+    assert balances['1ST']['free'] == 9.0
+    assert balances['1ST']['total'] == 10.0
+    assert balances['1ST']['used'] == 1.0
+
+    assert balances['2ST']['free'] == 6.0
+    assert balances['2ST']['total'] == 10.0
+    assert balances['2ST']['used'] == 4.0
+
+    assert balances['3ST']['free'] == 7.0
+    assert balances['3ST']['total'] == 10.0
+    assert balances['3ST']['used'] == 3.0
+
+    assert balances['4ST']['free'] == 10.0
+    assert balances['4ST']['total'] == 10.0
+    assert balances['4ST']['used'] == 0.0
+    ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
+                           "get_balances", "fetch_balance")