Merge pull request #3799 from imxuwang/issue3783

Introduce Telegram /stats endpoint
This commit is contained in:
Matthias
2020-12-10 15:41:09 +01:00
committed by GitHub
9 changed files with 180 additions and 2 deletions

View File

@@ -198,6 +198,8 @@ class ApiServer(RPC):
self.app.add_url_rule(f'{BASE_URI}/logs', 'log', view_func=self._get_logs, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/profit', 'profit',
view_func=self._profit, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/stats', 'stats',
view_func=self._stats, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/performance', 'performance',
view_func=self._performance, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/status', 'status',
@@ -417,6 +419,18 @@ class ApiServer(RPC):
return jsonify(stats)
@require_login
@rpc_catch_errors
def _stats(self):
"""
Handler for /stats.
Returns a Object with "durations" and "sell_reasons" as keys.
"""
stats = self._rpc_stats()
return jsonify(stats)
@require_login
@rpc_catch_errors
def _performance(self):

View File

@@ -275,6 +275,39 @@ class RPC:
"trades_count": len(output)
}
def _rpc_stats(self) -> Dict[str, Any]:
"""
Generate generic stats for trades in database
"""
def trade_win_loss(trade):
if trade.close_profit > 0:
return 'wins'
elif trade.close_profit < 0:
return 'losses'
else:
return 'draws'
trades = trades = Trade.get_trades([Trade.is_open.is_(False)])
# Sell reason
sell_reasons = {}
for trade in trades:
if trade.sell_reason not in sell_reasons:
sell_reasons[trade.sell_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
sell_reasons[trade.sell_reason][trade_win_loss(trade)] += 1
# Duration
dur: Dict[str, List[int]] = {'wins': [], 'draws': [], 'losses': []}
for trade in trades:
if trade.close_date is not None and trade.open_date is not None:
trade_dur = (trade.close_date - trade.open_date).total_seconds()
dur[trade_win_loss(trade)].append(trade_dur)
wins_dur = sum(dur['wins']) / len(dur['wins']) if len(dur['wins']) > 0 else 'N/A'
draws_dur = sum(dur['draws']) / len(dur['draws']) if len(dur['draws']) > 0 else 'N/A'
losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else 'N/A'
durations = {'wins': wins_dur, 'draws': draws_dur, 'losses': losses_dur}
return {'sell_reasons': sell_reasons, 'durations': durations}
def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
""" Returns cumulative profit statistics """

View File

@@ -5,6 +5,7 @@ This module manage Telegram communication
"""
import json
import logging
from datetime import timedelta
from typing import Any, Callable, Dict, List, Union
import arrow
@@ -98,6 +99,7 @@ class Telegram(RPC):
CommandHandler('trades', self._trades),
CommandHandler('delete', self._delete_trade),
CommandHandler('performance', self._performance),
CommandHandler('stats', self._stats),
CommandHandler('daily', self._daily),
CommandHandler('count', self._count),
CommandHandler('locks', self._locks),
@@ -388,6 +390,48 @@ class Telegram(RPC):
f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`")
self._send_msg(markdown_msg)
@authorized_only
def _stats(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /stats
Show stats of recent trades
"""
stats = self._rpc_stats()
reason_map = {
'roi': 'ROI',
'stop_loss': 'Stoploss',
'trailing_stop_loss': 'Trail. Stop',
'stoploss_on_exchange': 'Stoploss',
'sell_signal': 'Sell Signal',
'force_sell': 'Forcesell',
'emergency_sell': 'Emergency Sell',
}
sell_reasons_tabulate = [
[
reason_map.get(reason, reason),
sum(count.values()),
count['wins'],
count['losses']
] for reason, count in stats['sell_reasons'].items()
]
sell_reasons_msg = tabulate(
sell_reasons_tabulate,
headers=['Sell Reason', 'Sells', 'Wins', 'Losses']
)
durations = stats['durations']
duration_msg = tabulate([
['Wins', str(timedelta(seconds=durations['wins']))
if durations['wins'] != 'N/A' else 'N/A'],
['Losses', str(timedelta(seconds=durations['losses']))
if durations['losses'] != 'N/A' else 'N/A']
],
headers=['', 'Avg. Duration']
)
msg = (f"""```\n{sell_reasons_msg}```\n```\n{duration_msg}```""")
self._send_msg(msg, ParseMode.MARKDOWN)
@authorized_only
def _balance(self, update: Update, context: CallbackContext) -> None:
""" Handler for /balance """
@@ -743,6 +787,8 @@ class Telegram(RPC):
"*/delete <trade_id>:* `Instantly delete the given trade in the database`\n"
"*/performance:* `Show performance of each finished trade grouped by pair`\n"
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n"
"*/stats:* `Shows Wins / losses by Sell reason as well as "
"Avg. holding durationsfor buys and sells.`\n"
"*/count:* `Show number of active trades compared to allowed number of trades`\n"
"*/locks:* `Show currently locked pairs`\n"
"*/balance:* `Show account balance per currency`\n"