Merge pull request #2256 from freqtrade/kraken_balance

fix Kraken balance calculation
This commit is contained in:
hroff-1902 2019-09-12 23:12:55 +03:00 committed by GitHub
commit c5f455d660
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 4 deletions

View File

@ -30,9 +30,6 @@ BAD_EXCHANGES = {
"bitmex": "Various reasons", "bitmex": "Various reasons",
"bitstamp": "Does not provide history. " "bitstamp": "Does not provide history. "
"Details in https://github.com/freqtrade/freqtrade/issues/1983", "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"
} }

View File

@ -2,7 +2,11 @@
import logging import logging
from typing import Dict from typing import Dict
import ccxt
from freqtrade import OperationalException, TemporaryError
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange import retrier
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -10,3 +14,33 @@ logger = logging.getLogger(__name__)
class Kraken(Exchange): class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"} _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

View File

@ -867,7 +867,7 @@ def test_get_balance_dry_run(default_conf, mocker):
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_balance_prod(default_conf, mocker, exchange_name): def test_get_balance_prod(default_conf, mocker, exchange_name):
api_mock = MagicMock() 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 default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) 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:.*'): with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'):
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) 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.Exchange.get_balances', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Kraken.get_balances', MagicMock(return_value={}))
exchange.get_balance(currency='BTC') exchange.get_balance(currency='BTC')

View File

@ -4,6 +4,7 @@ from random import randint
from unittest.mock import MagicMock from unittest.mock import MagicMock
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
def test_buy_kraken_trading_agreement(default_conf, mocker): 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][3] == 1
assert api_mock.create_order.call_args[0][4] is None assert api_mock.create_order.call_args[0][4] is None
assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'} 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")