Merge pull request #2533 from xmatthias/rpc/balance

/Balance rework
This commit is contained in:
hroff-1902 2019-11-27 18:39:18 +03:00 committed by GitHub
commit 7a52334c9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 105 deletions

View File

@ -354,7 +354,8 @@ class ApiServer(RPC):
Returns the current status of the trades in json format Returns the current status of the trades in json format
""" """
results = self._rpc_balance(self._config.get('fiat_display_currency', '')) results = self._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', ''))
return self.rest_dump(results) return self.rest_dump(results)
@require_login @require_login

View File

@ -297,34 +297,42 @@ class RPC:
'best_rate': round(bp_rate * 100, 2), 'best_rate': round(bp_rate * 100, 2),
} }
def _rpc_balance(self, fiat_display_currency: str) -> Dict: def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
""" Returns current account balance per crypto """ """ Returns current account balance per crypto """
output = [] output = []
total = 0.0 total = 0.0
for coin, balance in self._freqtrade.exchange.get_balances().items(): try:
if not balance['total']: tickers = self._freqtrade.exchange.get_tickers()
except (TemporaryError, DependencyException):
raise RPCException('Error getting current tickers.')
for coin, balance in self._freqtrade.wallets.get_all_balances().items():
if not balance.total:
continue continue
if coin == 'BTC': est_stake: float = 0
if coin == stake_currency:
rate = 1.0 rate = 1.0
est_stake = balance.total
else: else:
try: try:
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, "BTC") pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency)
if pair.startswith("BTC"): rate = tickers.get(pair, {}).get('bid', None)
rate = 1.0 / self._freqtrade.get_sell_rate(pair, False) if rate:
else: if pair.startswith(stake_currency):
rate = self._freqtrade.get_sell_rate(pair, False) rate = 1.0 / rate
est_stake = rate * balance.total
except (TemporaryError, DependencyException): except (TemporaryError, DependencyException):
logger.warning(f" Could not get rate for pair {coin}.") logger.warning(f" Could not get rate for pair {coin}.")
continue continue
est_btc: float = rate * balance['total'] total = total + (est_stake or 0)
total = total + est_btc
output.append({ output.append({
'currency': coin, 'currency': coin,
'free': balance['free'] if balance['free'] is not None else 0, 'free': balance.free if balance.free is not None else 0,
'balance': balance['total'] if balance['total'] is not None else 0, 'balance': balance.total if balance.total is not None else 0,
'used': balance['used'] if balance['used'] is not None else 0, 'used': balance.used if balance.used is not None else 0,
'est_btc': est_btc, 'est_stake': est_stake or 0,
'stake': stake_currency,
}) })
if total == 0.0: if total == 0.0:
if self._freqtrade.config.get('dry_run', False): if self._freqtrade.config.get('dry_run', False):

View File

@ -325,15 +325,16 @@ class Telegram(RPC):
def _balance(self, update: Update, context: CallbackContext) -> None: def _balance(self, update: Update, context: CallbackContext) -> None:
""" Handler for /balance """ """ Handler for /balance """
try: try:
result = self._rpc_balance(self._config.get('fiat_display_currency', '')) result = self._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', ''))
output = '' output = ''
for currency in result['currencies']: for currency in result['currencies']:
if currency['est_btc'] > 0.0001: if currency['est_stake'] > 0.0001:
curr_output = "*{currency}:*\n" \ curr_output = "*{currency}:*\n" \
"\t`Available: {free: .8f}`\n" \ "\t`Available: {free: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \ "\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {used: .8f}`\n" \ "\t`Pending: {used: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency) "\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency)
else: else:
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency) curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)

View File

@ -2,7 +2,7 @@
""" Wallet """ """ Wallet """
import logging import logging
from typing import Dict, NamedTuple from typing import Dict, NamedTuple, Any
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade import constants from freqtrade import constants
@ -72,3 +72,6 @@ class Wallets:
) )
logger.info('Wallets synced.') logger.info('Wallets synced.')
def get_all_balances(self) -> Dict[str, Any]:
return self._wallets

View File

@ -980,6 +980,28 @@ def tickers():
'quoteVolume': 62.68220262, 'quoteVolume': 62.68220262,
'info': {} 'info': {}
}, },
'BTC/USDT': {
'symbol': 'BTC/USDT',
'timestamp': 1573758371399,
'datetime': '2019-11-14T19:06:11.399Z',
'high': 8800.0,
'low': 8582.6,
'bid': 8648.16,
'bidVolume': 0.238771,
'ask': 8648.72,
'askVolume': 0.016253,
'vwap': 8683.13647806,
'open': 8759.7,
'close': 8648.72,
'last': 8648.72,
'previousClose': 8759.67,
'change': -110.98,
'percentage': -1.267,
'average': None,
'baseVolume': 35025.943355,
'quoteVolume': 304135046.4242901,
'info': {}
},
'ETH/USDT': { 'ETH/USDT': {
'symbol': 'ETH/USDT', 'symbol': 'ETH/USDT',
'timestamp': 1522014804118, 'timestamp': 1522014804118,
@ -1067,7 +1089,29 @@ def tickers():
'baseVolume': 59698.79897, 'baseVolume': 59698.79897,
'quoteVolume': 29132399.743954, 'quoteVolume': 29132399.743954,
'info': {} 'info': {}
} },
'XRP/BTC': {
'symbol': 'XRP/BTC',
'timestamp': 1573758257534,
'datetime': '2019-11-14T19:04:17.534Z',
'high': 3.126e-05,
'low': 3.061e-05,
'bid': 3.093e-05,
'bidVolume': 27901.0,
'ask': 3.095e-05,
'askVolume': 10551.0,
'vwap': 3.091e-05,
'open': 3.119e-05,
'close': 3.094e-05,
'last': 3.094e-05,
'previousClose': 3.117e-05,
'change': -2.5e-07,
'percentage': -0.802,
'average': None,
'baseVolume': 37334921.0,
'quoteVolume': 1154.19266394,
'info': {}
},
}) })
@ -1317,8 +1361,8 @@ def rpc_balance():
'used': 0.0 'used': 0.0
}, },
'XRP': { 'XRP': {
'total': 1.0, 'total': 0.1,
'free': 1.0, 'free': 0.01,
'used': 0.0 'used': 0.0
}, },
'EUR': { 'EUR': {

View File

@ -100,7 +100,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
markets=PropertyMock(return_value=shitcoinmarkets), markets=PropertyMock(return_value=shitcoinmarkets),
) )
# argument: use the whitelist dynamically by exchange-volume # argument: use the whitelist dynamically by exchange-volume
whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC'] whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']
bot.pairlists.refresh_pairlist() bot.pairlists.refresh_pairlist()
assert whitelist == bot.pairlists.whitelist assert whitelist == bot.pairlists.whitelist
@ -135,10 +135,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
@pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [ @pytest.mark.parametrize("pairlists,base_currency,whitelist_result", [
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']),
# Different sorting depending on quote or bid volume # Different sorting depending on quote or bid volume
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}], ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}],
"BTC", ['HOT/BTC', 'FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), "BTC", ['HOT/BTC', 'FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}], ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}],
"USDT", ['ETH/USDT']), "USDT", ['ETH/USDT']),
# No pair for ETH ... # No pair for ETH ...
@ -146,19 +146,19 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
"ETH", []), "ETH", []),
# Precisionfilter and quote volume # Precisionfilter and quote volume
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), {"method": "PrecisionFilter"}], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
# Precisionfilter bid # Precisionfilter bid
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"}, ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "bidVolume"},
{"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'LTC/BTC', 'TKN/BTC', 'ETH/BTC']), {"method": "PrecisionFilter"}], "BTC", ['FUEL/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
# PriceFilter and VolumePairList # PriceFilter and VolumePairList
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "PriceFilter", "low_price_ratio": 0.03}], {"method": "PriceFilter", "low_price_ratio": 0.03}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'FUEL/BTC']), "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
# Hot is removed by precision_filter, Fuel by low_price_filter. # Hot is removed by precision_filter, Fuel by low_price_filter.
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, ([{"method": "VolumePairList", "number_assets": 6, "sort_key": "quoteVolume"},
{"method": "PrecisionFilter"}, {"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.02} {"method": "PriceFilter", "low_price_ratio": 0.02}
], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), ], "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
# StaticPairlist Only # StaticPairlist Only
([{"method": "StaticPairList"}, ([{"method": "StaticPairList"},
], "BTC", ['ETH/BTC', 'TKN/BTC']), ], "BTC", ['ETH/BTC', 'TKN/BTC']),

View File

@ -355,29 +355,18 @@ def test_rpc_balance_handle_error(default_conf, mocker):
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=mock_balance), get_balances=MagicMock(return_value=mock_balance),
get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
with pytest.raises(RPCException, match="Error getting current tickers."):
result = rpc._rpc_balance(default_conf['fiat_display_currency']) rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12)
assert prec_satoshi(result['value'], 180000)
assert 'USD' == result['symbol']
assert result['currencies'] == [{
'currency': 'BTC',
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_btc': 12.0,
}]
assert result['total'] == 12.0
def test_rpc_balance_handle(default_conf, mocker): def test_rpc_balance_handle(default_conf, mocker, tickers):
mock_balance = { mock_balance = {
'BTC': { 'BTC': {
'free': 10.0, 'free': 10.0,
@ -389,7 +378,7 @@ def test_rpc_balance_handle(default_conf, mocker):
'total': 5.0, 'total': 5.0,
'used': 4.0, 'used': 4.0,
}, },
'PAX': { 'USDT': {
'free': 5.0, 'free': 5.0,
'total': 10.0, 'total': 10.0,
'used': 5.0, 'used': 5.0,
@ -405,10 +394,9 @@ def test_rpc_balance_handle(default_conf, mocker):
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=mock_balance), get_balances=MagicMock(return_value=mock_balance),
get_ticker=MagicMock( get_tickers=tickers,
side_effect=lambda p, r: {'bid': 100} if p == "BTC/PAX" else {'bid': 0.01}),
get_valid_pair_combination=MagicMock( get_valid_pair_combination=MagicMock(
side_effect=lambda a, b: f"{b}/{a}" if a == "PAX" else f"{a}/{b}") side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}")
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@ -416,30 +404,35 @@ def test_rpc_balance_handle(default_conf, mocker):
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
result = rpc._rpc_balance(default_conf['fiat_display_currency']) result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12.15) assert prec_satoshi(result['total'], 12.309096315)
assert prec_satoshi(result['value'], 182250) assert prec_satoshi(result['value'], 184636.44472997)
assert 'USD' == result['symbol'] assert 'USD' == result['symbol']
assert result['currencies'] == [ assert result['currencies'] == [
{'currency': 'BTC', {'currency': 'BTC',
'free': 10.0, 'free': 10.0,
'balance': 12.0, 'balance': 12.0,
'used': 2.0, 'used': 2.0,
'est_btc': 12.0, 'est_stake': 12.0,
'stake': 'BTC',
}, },
{'free': 1.0, {'free': 1.0,
'balance': 5.0, 'balance': 5.0,
'currency': 'ETH', 'currency': 'ETH',
'est_btc': 0.05, 'est_stake': 0.30794,
'used': 4.0 'used': 4.0,
'stake': 'BTC',
}, },
{'free': 5.0, {'free': 5.0,
'balance': 10.0, 'balance': 10.0,
'currency': 'PAX', 'currency': 'USDT',
'est_btc': 0.1, 'est_stake': 0.0011563153318162476,
'used': 5.0} 'used': 5.0,
'stake': 'BTC',
}
] ]
assert result['total'] == 12.15 assert result['total'] == 12.309096315331816
def test_rpc_start(mocker, default_conf) -> None: def test_rpc_start(mocker, default_conf) -> None:

View File

@ -224,28 +224,10 @@ def test_api_stopbuy(botclient):
def test_api_balance(botclient, mocker, rpc_balance): def test_api_balance(botclient, mocker, rpc_balance):
ftbot, client = botclient ftbot, client = botclient
def mock_ticker(symbol, refresh):
if symbol == 'BTC/USDT':
return {
'bid': 10000.00,
'ask': 10000.00,
'last': 10000.00,
}
elif symbol == 'XRP/BTC':
return {
'bid': 0.00001,
'ask': 0.00001,
'last': 0.00001,
}
return {
'bid': 0.1,
'ask': 0.1,
'last': 0.1,
}
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination',
side_effect=lambda a, b: f"{a}/{b}") side_effect=lambda a, b: f"{a}/{b}")
ftbot.wallets.update()
rc = client_get(client, f"{BASE_URI}/balance") rc = client_get(client, f"{BASE_URI}/balance")
assert_response(rc) assert_response(rc)
@ -256,7 +238,8 @@ def test_api_balance(botclient, mocker, rpc_balance):
'free': 12.0, 'free': 12.0,
'balance': 12.0, 'balance': 12.0,
'used': 0.0, 'used': 0.0,
'est_btc': 12.0, 'est_stake': 12.0,
'stake': 'BTC',
} }

View File

@ -461,29 +461,10 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance) -> None: def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None:
def mock_ticker(symbol, refresh):
if symbol == 'BTC/USDT':
return {
'bid': 10000.00,
'ask': 10000.00,
'last': 10000.00,
}
elif symbol == 'XRP/BTC':
return {
'bid': 0.00001,
'ask': 0.00001,
'last': 0.00001,
}
return {
'bid': 0.1,
'ask': 0.1,
'last': 0.1,
}
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination', mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination',
side_effect=lambda a, b: f"{a}/{b}") side_effect=lambda a, b: f"{a}/{b}")
@ -564,7 +545,8 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'free': 1.0, 'free': 1.0,
'used': 0.5, 'used': 0.5,
'balance': i, 'balance': i,
'est_btc': 1 'est_stake': 1,
'stake': 'BTC',
}) })
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={ mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
'currencies': balances, 'currencies': balances,