Merge pull request #8537 from freqtrade/feat/balance_improve

Improve balance output
This commit is contained in:
Matthias 2023-04-24 14:26:05 +02:00 committed by GitHub
commit 4690810d5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 97 additions and 28 deletions

View File

@ -191,7 +191,8 @@ official commands. You can ask at any moment for help with `/help`.
| **Metrics** |
| `/profit [<n>]` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default)
| `/performance` | Show performance of each finished trade grouped by pair
| `/balance` | Show account balance per currency
| `/balance` | Show bot managed balance per currency
| `/balance full` | Show account balance per currency
| `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
| `/weekly <n>` | Shows profit or loss per week, over the last n weeks (n defaults to 8)
| `/monthly <n>` | Shows profit or loss per month, over the last n months (n defaults to 6)
@ -202,7 +203,6 @@ official commands. You can ask at any moment for help with `/help`.
| `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
| `/edge` | Show validated pairs by Edge if it is enabled.
## Telegram commands in action
Below, example of Telegram message you will receive for each command.

View File

@ -36,20 +36,25 @@ class Balance(BaseModel):
free: float
balance: float
used: float
bot_owned: Optional[float]
est_stake: float
est_stake_bot: Optional[float]
stake: str
# Starting with 2.x
side: str
leverage: float
is_position: bool
position: float
is_bot_managed: bool
class Balances(BaseModel):
currencies: List[Balance]
total: float
total_bot: float
symbol: str
value: float
value_bot: float
stake: str
note: str
starting_capital: float

View File

@ -43,7 +43,8 @@ logger = logging.getLogger(__name__)
# 2.23: Allow plot config request in webserver mode
# 2.24: Add cancel_open_order endpoint
# 2.25: Add several profit values to /status endpoint
API_VERSION = 2.25
# 2.26: increase /balance output
API_VERSION = 2.26
# Public API, requires no auth.
router_public = APIRouter()

View File

@ -583,13 +583,16 @@ class RPC:
}
def __balance_get_est_stake(
self, coin: str, stake_currency: str, balance: Wallet, tickers) -> float:
self, coin: str, stake_currency: str, amount: float,
balance: Wallet, tickers) -> Tuple[float, float]:
est_stake = 0.0
est_bot_stake = 0.0
if coin == stake_currency:
est_stake = balance.total
if self._config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
# in Futures, "total" includes the locked stake, and therefore all positions
est_stake = balance.free
est_bot_stake = amount
else:
try:
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency)
@ -598,11 +601,12 @@ class RPC:
if pair.startswith(stake_currency) and not pair.endswith(stake_currency):
rate = 1.0 / rate
est_stake = rate * balance.total
est_bot_stake = rate * amount
except (ExchangeError):
logger.warning(f"Could not get rate for pair {coin}.")
raise ValueError()
return est_stake
return est_stake, est_bot_stake
def _rpc_balance(self, stake_currency: str, fiat_display_currency: str) -> Dict:
""" Returns current account balance per crypto """
@ -615,7 +619,7 @@ class RPC:
raise RPCException('Error getting current tickers.')
open_trades: List[Trade] = Trade.get_open_trades()
open_assets = [t.base_currency for t in open_trades]
open_assets: Dict[str, Trade] = {t.safe_base_currency: t for t in open_trades}
self._freqtrade.wallets.update(require_update=False)
starting_capital = self._freqtrade.wallets.get_starting_balance()
starting_cap_fiat = self._fiat_converter.convert_amount(
@ -625,30 +629,43 @@ class RPC:
for coin, balance in self._freqtrade.wallets.get_all_balances().items():
if not balance.total:
continue
trade = open_assets.get(coin, None)
is_bot_managed = coin == stake_currency or trade is not None
trade_amount = trade.amount if trade else 0
if coin == stake_currency:
trade_amount = self._freqtrade.wallets.get_available_stake_amount()
try:
est_stake = self.__balance_get_est_stake(coin, stake_currency, balance, tickers)
est_stake, est_stake_bot = self.__balance_get_est_stake(
coin, stake_currency, trade_amount, balance, tickers)
except ValueError:
continue
total += est_stake
if coin == stake_currency or coin in open_assets:
total_bot += est_stake
if is_bot_managed:
total_bot += est_stake_bot
currencies.append({
'currency': coin,
'free': balance.free,
'balance': balance.total,
'used': balance.used,
'bot_owned': trade_amount,
'est_stake': est_stake or 0,
'est_stake_bot': est_stake_bot if is_bot_managed else 0,
'stake': stake_currency,
'side': 'long',
'leverage': 1,
'position': 0,
'is_bot_managed': is_bot_managed,
'is_position': False,
})
symbol: str
position: PositionWallet
for symbol, position in self._freqtrade.wallets.get_all_positions().items():
total += position.collateral
total_bot += position.collateral
currencies.append({
'currency': symbol,
@ -657,9 +674,11 @@ class RPC:
'used': 0,
'position': position.position,
'est_stake': position.collateral,
'est_stake_bot': position.collateral,
'stake': stake_currency,
'leverage': position.leverage,
'side': position.side,
'is_bot_managed': True,
'is_position': True
})
@ -675,8 +694,10 @@ class RPC:
return {
'currencies': currencies,
'total': total,
'total_bot': total_bot,
'symbol': fiat_display_currency,
'value': value,
'value_bot': value_bot,
'stake': stake_currency,
'starting_capital': starting_capital,
'starting_capital_ratio': starting_capital_ratio,

View File

@ -905,6 +905,7 @@ class Telegram(RPCHandler):
@authorized_only
def _balance(self, update: Update, context: CallbackContext) -> None:
""" Handler for /balance """
full_result = context.args and 'full' in context.args
result = self._rpc._rpc_balance(self._config['stake_currency'],
self._config.get('fiat_display_currency', ''))
@ -915,8 +916,7 @@ class Telegram(RPCHandler):
output = ''
if self._config['dry_run']:
output += "*Warning:* Simulated balances in Dry Mode.\n"
starting_cap = round_coin_value(
result['starting_capital'], self._config['stake_currency'])
starting_cap = round_coin_value(result['starting_capital'], self._config['stake_currency'])
output += f"Starting capital: `{starting_cap}`"
starting_cap_fiat = round_coin_value(
result['starting_capital_fiat'], self._config['fiat_display_currency']
@ -928,7 +928,10 @@ class Telegram(RPCHandler):
total_dust_currencies = 0
for curr in result['currencies']:
curr_output = ''
if curr['est_stake'] > balance_dust_level:
if (
(curr['is_position'] or curr['est_stake'] > balance_dust_level)
and (full_result or curr['is_bot_managed'])
):
if curr['is_position']:
curr_output = (
f"*{curr['currency']}:*\n"
@ -937,13 +940,17 @@ class Telegram(RPCHandler):
f"\t`Est. {curr['stake']}: "
f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n")
else:
est_stake = round_coin_value(
curr['est_stake' if full_result else 'est_stake_bot'], curr['stake'], False)
curr_output = (
f"*{curr['currency']}:*\n"
f"\t`Available: {curr['free']:.8f}`\n"
f"\t`Balance: {curr['balance']:.8f}`\n"
f"\t`Pending: {curr['used']:.8f}`\n"
f"\t`Est. {curr['stake']}: "
f"{round_coin_value(curr['est_stake'], curr['stake'], False)}`\n")
f"\t`Bot Owned: {curr['bot_owned']:.8f}`\n"
f"\t`Est. {curr['stake']}: {est_stake}`\n")
elif curr['est_stake'] <= balance_dust_level:
total_dust_balance += curr['est_stake']
total_dust_currencies += 1
@ -965,14 +972,15 @@ class Telegram(RPCHandler):
tc = result['trade_count'] > 0
stake_improve = f" `({result['starting_capital_ratio']:.2%})`" if tc else ''
fiat_val = f" `({result['starting_capital_fiat_ratio']:.2%})`" if tc else ''
output += ("\n*Estimated Value*:\n"
f"\t`{result['stake']}: "
f"{round_coin_value(result['total'], result['stake'], False)}`"
f"{stake_improve}\n"
f"\t`{result['symbol']}: "
f"{round_coin_value(result['value'], result['symbol'], False)}`"
f"{fiat_val}\n")
value = round_coin_value(
result['value' if full_result else 'value_bot'], result['symbol'], False)
total_stake = round_coin_value(
result['total' if full_result else 'total_bot'], result['stake'], False)
output += (
f"\n*Estimated Value{' (Bot managed assets only)' if not full_result else ''}*:\n"
f"\t`{result['stake']}: {total_stake}`{stake_improve}\n"
f"\t`{result['symbol']}: {value}`{fiat_val}\n"
)
self._send_msg(output, reload_able=True, callback_path="update_balance",
query=update.callback_query)
@ -1528,7 +1536,8 @@ class Telegram(RPCHandler):
"------------\n"
"*/show_config:* `Show running configuration` \n"
"*/locks:* `Show currently locked pairs`\n"
"*/balance:* `Show account balance per currency`\n"
"*/balance:* `Show bot managed balance per currency`\n"
"*/balance total:* `Show account balance per currency`\n"
"*/logs [limit]:* `Show latest logs - defaults to 10` \n"
"*/count:* `Show number of active trades compared to allowed number of trades`\n"
"*/edge:* `Shows validated pairs by Edge if it is enabled` \n"

View File

@ -546,53 +546,67 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'bot_owned': 9.9, # available stake - reducing by reserved amount
'est_stake': 10.0, # In futures mode, "free" is used here.
'est_stake_bot': 9.9,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': True,
},
{
'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'bot_owned': 0,
'est_stake': 0.30794,
'est_stake_bot': 0,
'used': 4.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': False,
},
{
'free': 5.0,
'balance': 10.0,
'currency': 'USDT',
'bot_owned': 0,
'est_stake': 0.0011562404610161968,
'est_stake_bot': 0,
'used': 5.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': False,
},
{
'free': 0.0,
'balance': 0.0,
'currency': 'ETH/USDT:USDT',
'est_stake': 20,
'est_stake_bot': 20,
'used': 0,
'stake': 'BTC',
'is_position': True,
'leverage': 5.0,
'position': 1000.0,
'side': 'short',
'is_bot_managed': True,
}
]
assert pytest.approx(result['total_bot']) == 29.9
assert pytest.approx(result['total']) == 30.309096
assert result['starting_capital'] == 10
assert result['starting_capital_ratio'] == 0.0
# Very high starting capital ratio, because the futures position really has the wrong unit.
# TODO: improve this test (see comment above)
assert result['starting_capital_ratio'] == pytest.approx(1.98999999)
def test_rpc_start(mocker, default_conf) -> None:

View File

@ -480,13 +480,18 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
'free': 12.0,
'balance': 12.0,
'used': 0.0,
'bot_owned': pytest.approx(11.879999),
'est_stake': 12.0,
'est_stake_bot': pytest.approx(11.879999),
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': True,
}
assert response['total'] == 12.159513094
assert response['total_bot'] == pytest.approx(11.879999)
assert 'starting_capital' in response
assert 'starting_capital_fiat' in response
assert 'starting_capital_pct' in response

View File

@ -783,19 +783,28 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
patch_get_signal(freqtradebot)
telegram._balance(update=update, context=MagicMock())
context = MagicMock()
context.args = ["full"]
telegram._balance(update=update, context=context)
result = msg_mock.call_args_list[0][0][0]
assert msg_mock.call_count == 1
result_full = msg_mock.call_args_list[1][0][0]
assert msg_mock.call_count == 2
assert '*BTC:*' in result
assert '*ETH:*' not in result
assert '*USDT:*' not in result
assert '*EUR:*' not in result
assert '*LTC:*' in result
assert '*LTC:*' not in result
assert '*LTC:*' in result_full
assert '*XRP:*' not in result
assert 'Balance:' in result
assert 'Est. BTC:' in result
assert 'BTC: 12' in result
assert 'BTC: 11' in result
assert 'BTC: 12' in result_full
assert "*3 Other Currencies (< 0.0001 BTC):*" in result
assert 'BTC: 0.00000309' in result
assert '*Estimated Value*:' in result_full
assert '*Estimated Value (Bot managed assets only)*:' in result
def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
@ -834,18 +843,23 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'free': 1.0,
'used': 0.5,
'balance': i,
'bot_owned': 0.5,
'est_stake': 1,
'est_stake_bot': 1,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
'is_bot_managed': True,
})
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
'currencies': balances,
'total': 100.0,
'total_bot': 100.0,
'symbol': 100.0,
'value': 1000.0,
'value_bot': 1000.0,
'starting_capital': 1000,
'starting_capital_fiat': 1000,
})