mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge pull request #4361 from freqtrade/format_currencies
Format currencies
This commit is contained in:
commit
4b5f4aa1c1
|
@ -45,6 +45,16 @@ USERPATH_NOTEBOOKS = 'notebooks'
|
|||
|
||||
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
||||
|
||||
|
||||
# Define decimals per coin for outputs
|
||||
# Only used for outputs.
|
||||
DECIMAL_PER_COIN_FALLBACK = 3 # Should be low to avoid listing all possible FIAT's
|
||||
DECIMALS_PER_COIN = {
|
||||
'BTC': 8,
|
||||
'ETH': 5,
|
||||
}
|
||||
|
||||
|
||||
# Soure files with destination directories within user-directory
|
||||
USER_DATA_FILES = {
|
||||
'sample_strategy.py': USERPATH_STRATEGIES,
|
||||
|
|
|
@ -11,10 +11,35 @@ from typing.io import IO
|
|||
|
||||
import rapidjson
|
||||
|
||||
from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def decimals_per_coin(coin: str):
|
||||
"""
|
||||
Helper method getting decimal amount for this coin
|
||||
example usage: f".{decimals_per_coin('USD')}f"
|
||||
:param coin: Which coin are we printing the price / value for
|
||||
"""
|
||||
return DECIMALS_PER_COIN.get(coin, DECIMAL_PER_COIN_FALLBACK)
|
||||
|
||||
|
||||
def round_coin_value(value: float, coin: str, show_coin_name=True) -> str:
|
||||
"""
|
||||
Get price value for this coin
|
||||
:param value: Value to be printed
|
||||
:param coin: Which coin are we printing the price / value for
|
||||
:param show_coin_name: Return string in format: "222.22 USDT" or "222.22"
|
||||
:return: Formatted / rounded value (with or without coin name)
|
||||
"""
|
||||
if show_coin_name:
|
||||
return f"{value:.{decimals_per_coin(coin)}f} {coin}"
|
||||
else:
|
||||
return f"{value:.{decimals_per_coin(coin)}f}"
|
||||
|
||||
|
||||
def shorten_date(_date: str) -> str:
|
||||
"""
|
||||
Trim the date so it fits on small screens
|
||||
|
|
|
@ -10,7 +10,7 @@ from tabulate import tabulate
|
|||
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
|
||||
from freqtrade.data.btanalysis import calculate_market_change, calculate_max_drawdown
|
||||
from freqtrade.misc import file_dump_json
|
||||
from freqtrade.misc import decimals_per_coin, file_dump_json, round_coin_value
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -38,11 +38,12 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N
|
|||
file_dump_json(latest_filename, {'latest_backtest': str(filename.name)})
|
||||
|
||||
|
||||
def _get_line_floatfmt() -> List[str]:
|
||||
def _get_line_floatfmt(stake_currency: str) -> List[str]:
|
||||
"""
|
||||
Generate floatformat (goes in line with _generate_result_line())
|
||||
"""
|
||||
return ['s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', 'd', 'd', 'd']
|
||||
return ['s', 'd', '.2f', '.2f', f'.{decimals_per_coin(stake_currency)}f',
|
||||
'.2f', 'd', 'd', 'd', 'd']
|
||||
|
||||
|
||||
def _get_line_header(first_column: str, stake_currency: str) -> List[str]:
|
||||
|
@ -352,7 +353,7 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
|
|||
"""
|
||||
|
||||
headers = _get_line_header('Pair', stake_currency)
|
||||
floatfmt = _get_line_floatfmt()
|
||||
floatfmt = _get_line_floatfmt(stake_currency)
|
||||
output = [[
|
||||
t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'],
|
||||
t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses']
|
||||
|
@ -383,7 +384,9 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
|
|||
|
||||
output = [[
|
||||
t['sell_reason'], t['trades'], t['wins'], t['draws'], t['losses'],
|
||||
t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], t['profit_total_pct'],
|
||||
t['profit_mean_pct'], t['profit_sum_pct'],
|
||||
round_coin_value(t['profit_total_abs'], stake_currency, False),
|
||||
t['profit_total_pct'],
|
||||
] for t in sell_reason_stats]
|
||||
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
|
||||
|
||||
|
@ -396,7 +399,7 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
|
|||
:param all_results: Dict of <Strategyname: DataFrame> containing results for all strategies
|
||||
:return: pretty printed table with tabulate as string
|
||||
"""
|
||||
floatfmt = _get_line_floatfmt()
|
||||
floatfmt = _get_line_floatfmt(stake_currency)
|
||||
headers = _get_line_header('Strategy', stake_currency)
|
||||
|
||||
output = [[
|
||||
|
|
|
@ -18,6 +18,7 @@ from telegram.utils.helpers import escape_markdown
|
|||
|
||||
from freqtrade.__init__ import __version__
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.misc import round_coin_value
|
||||
from freqtrade.rpc import RPC, RPCException, RPCHandler, RPCMessageType
|
||||
|
||||
|
||||
|
@ -189,14 +190,14 @@ class Telegram(RPCHandler):
|
|||
else:
|
||||
msg['stake_amount_fiat'] = 0
|
||||
|
||||
message = ("\N{LARGE BLUE CIRCLE} *{exchange}:* Buying {pair}\n"
|
||||
"*Amount:* `{amount:.8f}`\n"
|
||||
"*Open Rate:* `{limit:.8f}`\n"
|
||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||
"*Total:* `({stake_amount:.6f} {stake_currency}").format(**msg)
|
||||
message = (f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}\n"
|
||||
f"*Amount:* `{msg['amount']:.8f}`\n"
|
||||
f"*Open Rate:* `{msg['limit']:.8f}`\n"
|
||||
f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
|
||||
f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}")
|
||||
|
||||
if msg.get('fiat_currency', None):
|
||||
message += ", {stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
|
||||
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||
message += ")`"
|
||||
|
||||
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
|
||||
|
@ -365,7 +366,7 @@ class Telegram(RPCHandler):
|
|||
)
|
||||
stats_tab = tabulate(
|
||||
[[day['date'],
|
||||
f"{day['abs_profit']:.8f} {stats['stake_currency']}",
|
||||
f"{round_coin_value(day['abs_profit'], stats['stake_currency'])}",
|
||||
f"{day['fiat_value']:.3f} {stats['fiat_display_currency']}",
|
||||
f"{day['trade_count']} trades"] for day in stats['data']],
|
||||
headers=[
|
||||
|
@ -415,18 +416,18 @@ class Telegram(RPCHandler):
|
|||
# Message to display
|
||||
if stats['closed_trade_count'] > 0:
|
||||
markdown_msg = ("*ROI:* Closed trades\n"
|
||||
f"∙ `{profit_closed_coin:.8f} {stake_cur} "
|
||||
f"∙ `{round_coin_value(profit_closed_coin, stake_cur)} "
|
||||
f"({profit_closed_percent_mean:.2f}%) "
|
||||
f"({profit_closed_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
||||
f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n")
|
||||
f"∙ `{round_coin_value(profit_closed_fiat, fiat_disp_cur)}`\n")
|
||||
else:
|
||||
markdown_msg = "`No closed trade` \n"
|
||||
|
||||
markdown_msg += (f"*ROI:* All trades\n"
|
||||
f"∙ `{profit_all_coin:.8f} {stake_cur} "
|
||||
f"∙ `{round_coin_value(profit_all_coin, stake_cur)} "
|
||||
f"({profit_all_percent_mean:.2f}%) "
|
||||
f"({profit_all_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
||||
f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n"
|
||||
f"∙ `{round_coin_value(profit_all_fiat, fiat_disp_cur)}`\n"
|
||||
f"*Total Trade Count:* `{trade_count}`\n"
|
||||
f"*First Trade opened:* `{first_trade_date}`\n"
|
||||
f"*Latest Trade opened:* `{latest_trade_date}\n`"
|
||||
|
@ -494,15 +495,17 @@ class Telegram(RPCHandler):
|
|||
"Starting capital: "
|
||||
f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
|
||||
)
|
||||
for currency in result['currencies']:
|
||||
if currency['est_stake'] > 0.0001:
|
||||
curr_output = ("*{currency}:*\n"
|
||||
"\t`Available: {free: .8f}`\n"
|
||||
"\t`Balance: {balance: .8f}`\n"
|
||||
"\t`Pending: {used: .8f}`\n"
|
||||
"\t`Est. {stake}: {est_stake: .8f}`\n").format(**currency)
|
||||
for curr in result['currencies']:
|
||||
if curr['est_stake'] > 0.0001:
|
||||
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")
|
||||
else:
|
||||
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
|
||||
curr_output = f"*{curr['currency']}:* not showing <1$ amount \n"
|
||||
|
||||
# Handle overflowing messsage length
|
||||
if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE_LENGTH:
|
||||
|
@ -512,8 +515,9 @@ class Telegram(RPCHandler):
|
|||
output += curr_output
|
||||
|
||||
output += ("\n*Estimated Value*:\n"
|
||||
"\t`{stake}: {total: .8f}`\n"
|
||||
"\t`{symbol}: {value: .2f}`\n").format(**result)
|
||||
f"\t`{result['stake']}: {result['total']: .8f}`\n"
|
||||
f"\t`{result['symbol']}: "
|
||||
f"{round_coin_value(result['value'], result['symbol'], False)}`\n")
|
||||
self._send_msg(output)
|
||||
except RPCException as e:
|
||||
self._send_msg(str(e))
|
||||
|
|
|
@ -519,7 +519,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
|
|||
assert '*EUR:*' in result
|
||||
assert 'Balance:' in result
|
||||
assert 'Est. BTC:' in result
|
||||
assert 'BTC: 12.00000000' in result
|
||||
assert 'BTC: 12.00000000' in result
|
||||
assert '*XRP:* not showing <1$ amount' in result
|
||||
|
||||
|
||||
|
@ -1205,7 +1205,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
|
|||
'*Amount:* `1333.33333333`\n' \
|
||||
'*Open Rate:* `0.00001099`\n' \
|
||||
'*Current Rate:* `0.00001099`\n' \
|
||||
'*Total:* `(0.001000 BTC, 12.345 USD)`'
|
||||
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
|
||||
|
||||
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
|
||||
caplog.clear()
|
||||
|
@ -1389,7 +1389,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
|||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Current Rate:* `0.00001099`\n'
|
||||
'*Total:* `(0.001000 BTC)`')
|
||||
'*Total:* `(0.00100000 BTC)`')
|
||||
|
||||
|
||||
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||
|
|
|
@ -6,9 +6,31 @@ from unittest.mock import MagicMock
|
|||
|
||||
import pytest
|
||||
|
||||
from freqtrade.misc import (file_dump_json, file_load_json, format_ms_time, pair_to_filename,
|
||||
plural, render_template, render_template_with_fallback,
|
||||
safe_value_fallback, safe_value_fallback2, shorten_date)
|
||||
from freqtrade.misc import (decimals_per_coin, file_dump_json, file_load_json, format_ms_time,
|
||||
pair_to_filename, plural, render_template,
|
||||
render_template_with_fallback, round_coin_value, safe_value_fallback,
|
||||
safe_value_fallback2, shorten_date)
|
||||
|
||||
|
||||
def test_decimals_per_coin():
|
||||
assert decimals_per_coin('USDT') == 3
|
||||
assert decimals_per_coin('EUR') == 3
|
||||
assert decimals_per_coin('BTC') == 8
|
||||
assert decimals_per_coin('ETH') == 5
|
||||
|
||||
|
||||
def test_round_coin_value():
|
||||
assert round_coin_value(222.222222, 'USDT') == '222.222 USDT'
|
||||
assert round_coin_value(222.2, 'USDT') == '222.200 USDT'
|
||||
assert round_coin_value(222.12745, 'EUR') == '222.127 EUR'
|
||||
assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC'
|
||||
assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH'
|
||||
|
||||
assert round_coin_value(222.222222, 'USDT', False) == '222.222'
|
||||
assert round_coin_value(222.2, 'USDT', False) == '222.200'
|
||||
assert round_coin_value(222.12745, 'EUR', False) == '222.127'
|
||||
assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121'
|
||||
assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745'
|
||||
|
||||
|
||||
def test_shorten_date() -> None:
|
||||
|
|
Loading…
Reference in New Issue
Block a user