Merge pull request #4982 from eschava/profit_day_week

day/week options for Telegram '/profit' command
This commit is contained in:
Matthias 2021-06-08 19:26:57 +01:00 committed by GitHub
commit e71d965e32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 27 deletions

View File

@ -123,7 +123,7 @@ Telegram is not mandatory. However, this is a great way to control your bot. Mor
- `/stop`: Stops the trader. - `/stop`: Stops the trader.
- `/stopbuy`: Stop entering new trades. - `/stopbuy`: Stop entering new trades.
- `/status <trade_id>|[table]`: Lists all or specific open trades. - `/status <trade_id>|[table]`: Lists all or specific open trades.
- `/profit`: Lists cumulative profit from all finished trades - `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days.
- `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`). - `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`).
- `/performance`: Show performance of each finished trade grouped by pair - `/performance`: Show performance of each finished trade grouped by pair
- `/balance`: Show account balance per currency. - `/balance`: Show account balance per currency.

View File

@ -163,7 +163,7 @@ official commands. You can ask at any moment for help with `/help`.
| `/count` | Displays number of trades used and available | `/count` | Displays number of trades used and available
| `/locks` | Show currently locked pairs. | `/locks` | Show currently locked pairs.
| `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id). | `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id).
| `/profit` | Display a summary of your profit/loss from close trades and some stats about your performance | `/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)
| `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`). | `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). | `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
| `/forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True) | `/forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)

View File

@ -355,9 +355,10 @@ class RPC:
return {'sell_reasons': sell_reasons, 'durations': durations} return {'sell_reasons': sell_reasons, 'durations': durations}
def _rpc_trade_statistics( def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]: self, stake_currency: str, fiat_display_currency: str,
start_date: datetime = datetime.fromtimestamp(0)) -> Dict[str, Any]:
""" Returns cumulative profit statistics """ """ Returns cumulative profit statistics """
trades = Trade.get_trades().order_by(Trade.id).all() trades = Trade.get_trades([Trade.open_date >= start_date]).order_by(Trade.id).all()
profit_all_coin = [] profit_all_coin = []
profit_all_ratio = [] profit_all_ratio = []

View File

@ -5,7 +5,8 @@ This module manage Telegram communication
""" """
import json import json
import logging import logging
from datetime import timedelta import re
from datetime import date, datetime, timedelta
from html import escape from html import escape
from itertools import chain from itertools import chain
from math import isnan from math import isnan
@ -99,23 +100,27 @@ class Telegram(RPCHandler):
# TODO: DRY! - its not good to list all valid cmds here. But otherwise # TODO: DRY! - its not good to list all valid cmds here. But otherwise
# this needs refacoring of the whole telegram module (same # this needs refacoring of the whole telegram module (same
# problem in _help()). # problem in _help()).
valid_keys: List[str] = ['/start', '/stop', '/status', '/status table', valid_keys: List[str] = [r'/start$', r'/stop$', r'/status$', r'/status table$',
'/trades', '/profit', '/performance', '/daily', r'/trades$', r'/performance$', r'/daily$', r'/daily \d+$',
'/stats', '/count', '/locks', '/balance', r'/profit$', r'/profit \d+',
'/stopbuy', '/reload_config', '/show_config', r'/stats$', r'/count$', r'/locks$', r'/balance$',
'/logs', '/whitelist', '/blacklist', '/edge', r'/stopbuy$', r'/reload_config$', r'/show_config$',
'/help', '/version'] r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$',
r'/forcebuy$', r'/help$', r'/version$']
# Create keys for generation
valid_keys_print = [k.replace('$', '') for k in valid_keys]
# custom keyboard specified in config.json # custom keyboard specified in config.json
cust_keyboard = self._config['telegram'].get('keyboard', []) cust_keyboard = self._config['telegram'].get('keyboard', [])
if cust_keyboard: if cust_keyboard:
combined = "(" + ")|(".join(valid_keys) + ")"
# check for valid shortcuts # check for valid shortcuts
invalid_keys = [b for b in chain.from_iterable(cust_keyboard) invalid_keys = [b for b in chain.from_iterable(cust_keyboard)
if b not in valid_keys] if not re.match(combined, b)]
if len(invalid_keys): if len(invalid_keys):
err_msg = ('config.telegram.keyboard: Invalid commands for ' err_msg = ('config.telegram.keyboard: Invalid commands for '
f'custom Telegram keyboard: {invalid_keys}' f'custom Telegram keyboard: {invalid_keys}'
f'\nvalid commands are: {valid_keys}') f'\nvalid commands are: {valid_keys_print}')
raise OperationalException(err_msg) raise OperationalException(err_msg)
else: else:
self._keyboard = cust_keyboard self._keyboard = cust_keyboard
@ -457,9 +462,20 @@ class Telegram(RPCHandler):
stake_cur = self._config['stake_currency'] stake_cur = self._config['stake_currency']
fiat_disp_cur = self._config.get('fiat_display_currency', '') fiat_disp_cur = self._config.get('fiat_display_currency', '')
start_date = datetime.fromtimestamp(0)
timescale = None
try:
if context.args:
timescale = int(context.args[0])
today_start = datetime.combine(date.today(), datetime.min.time())
start_date = today_start - timedelta(days=timescale)
except (TypeError, ValueError, IndexError):
pass
stats = self._rpc._rpc_trade_statistics( stats = self._rpc._rpc_trade_statistics(
stake_cur, stake_cur,
fiat_disp_cur) fiat_disp_cur,
start_date)
profit_closed_coin = stats['profit_closed_coin'] profit_closed_coin = stats['profit_closed_coin']
profit_closed_percent_mean = stats['profit_closed_percent_mean'] profit_closed_percent_mean = stats['profit_closed_percent_mean']
profit_closed_percent_sum = stats['profit_closed_percent_sum'] profit_closed_percent_sum = stats['profit_closed_percent_sum']
@ -487,13 +503,15 @@ class Telegram(RPCHandler):
else: else:
markdown_msg = "`No closed trade` \n" markdown_msg = "`No closed trade` \n"
markdown_msg += (f"*ROI:* All trades\n" markdown_msg += (
f"*ROI:* All trades\n"
f"∙ `{round_coin_value(profit_all_coin, stake_cur)} " f"∙ `{round_coin_value(profit_all_coin, stake_cur)} "
f"({profit_all_percent_mean:.2f}%) " f"({profit_all_percent_mean:.2f}%) "
f"({profit_all_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n" f"({profit_all_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
f"∙ `{round_coin_value(profit_all_fiat, fiat_disp_cur)}`\n" f"∙ `{round_coin_value(profit_all_fiat, fiat_disp_cur)}`\n"
f"*Total Trade Count:* `{trade_count}`\n" f"*Total Trade Count:* `{trade_count}`\n"
f"*First Trade opened:* `{first_trade_date}`\n" f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* "
f"`{first_trade_date}`\n"
f"*Latest Trade opened:* `{latest_trade_date}\n`" f"*Latest Trade opened:* `{latest_trade_date}\n`"
f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`" f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`"
) )
@ -959,7 +977,8 @@ class Telegram(RPCHandler):
" `pending buy orders are marked with an asterisk (*)`\n" " `pending buy orders are marked with an asterisk (*)`\n"
" `pending sell orders are marked with a double asterisk (**)`\n" " `pending sell orders are marked with a double asterisk (**)`\n"
"*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n"
"*/profit:* `Lists cumulative profit from all finished trades`\n" "*/profit [<n>]:* `Lists cumulative profit from all finished trades, "
"over the last n days`\n"
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " "*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, "
"regardless of profit`\n" "regardless of profit`\n"
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}"

View File

@ -1607,7 +1607,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
['/count', '/start', '/stop', '/help']] ['/count', '/start', '/stop', '/help']]
default_keyboard = ReplyKeyboardMarkup(default_keys_list) default_keyboard = ReplyKeyboardMarkup(default_keys_list)
custom_keys_list = [['/daily', '/stats', '/balance', '/profit'], custom_keys_list = [['/daily', '/stats', '/balance', '/profit', '/profit 5'],
['/count', '/start', '/reload_config', '/help']] ['/count', '/start', '/reload_config', '/help']]
custom_keyboard = ReplyKeyboardMarkup(custom_keys_list) custom_keyboard = ReplyKeyboardMarkup(custom_keys_list)
@ -1641,5 +1641,5 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
used_keyboard = bot.send_message.call_args[1]['reply_markup'] used_keyboard = bot.send_message.call_args[1]['reply_markup']
assert used_keyboard == custom_keyboard assert used_keyboard == custom_keyboard
assert log_has("using custom keyboard from config.json: " assert log_has("using custom keyboard from config.json: "
"[['/daily', '/stats', '/balance', '/profit'], ['/count', " "[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', "
"'/start', '/reload_config', '/help']]", caplog) "'/start', '/reload_config', '/help']]", caplog)