From c01953daf2a68f9e51fade461f87151c65255c81 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Sep 2019 06:57:58 +0200 Subject: [PATCH 1/4] Remove kraken block --- freqtrade/exchange/exchange.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d5c4a6b1c..d48d18ebf 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -30,9 +30,6 @@ BAD_EXCHANGES = { "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" } From 3b4bbe7a18718fa00f4aa7161ad7261da97d2677 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Sep 2019 06:58:10 +0200 Subject: [PATCH 2/4] Implement get_balances which uses open_orders --- freqtrade/exchange/kraken.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 91b41a159..df74a762d 100644 --- 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,30 @@ logger = logging.getLogger(__name__) 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"], 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 From f8eb1cd58a47ac6ca6fa21a5ebe8502013cb8796 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 11 Sep 2019 19:43:29 +0200 Subject: [PATCH 3/4] Add tests for kraken balance implementation --- tests/exchange/test_exchange.py | 3 +- tests/exchange/test_kraken.py | 82 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 5ab7cdb47..72b3af206 100644 --- 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 index ba94f8b45..3ad62d85a 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -4,6 +4,7 @@ from random import randint 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") From 6c5eff4a7c68d6bfeb9f6a0ed3cfdba9dc7bd833 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 12 Sep 2019 07:03:52 +0200 Subject: [PATCH 4/4] Use List of Tuples, remove unused columns --- freqtrade/exchange/kraken.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index df74a762d..6d3e82eca 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -29,8 +29,11 @@ class Kraken(Exchange): balances.pop("used", None) orders = self._api.fetch_open_orders() - order_list = [[x["symbol"].split("/")[0 if x["side"] == "sell" else 1], - x["remaining"], x["side"], x["amount"], ] for x in 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']