Repository: freqtrade/freqtrade
Pull request: Introduce Telegram /stats endpoint
## Summary
Add `/stats` command in Telegram

closes #3783

## Quick changelog

WIP



Problem statement:
Telegram Recommendations 
<!-- 
Note: this section will not show up in the issue.
Have you search for this feature before requesting it? It's highly likely that a similar request was already filed.
-->

## Describe your environment
(if applicable)

Operating system: UBUNTU 18.04
Python Version: 3.6.9 (python -V)
CCXT version: ccxt==1.33.72 (pip freeze | grep ccxt)
Freqtrade Version: develop-10c5b230 (freqtrade -V or docker-compose run --rm freqtrade -V for Freqtrade running in docker)


## Describe the enhancement

*Explain the enhancement you would like*

Backtesting is very important when making a strategy. As well, Live and Dry testing are more important and I think it would be really cool if Telegram had a stat box for sell reasons during testing mode like this. It could also help in determine what the strategy needs fixing.

/sellreason

Total Trades: 100
ROI Trades: 45
Stoploss Trades: 15
Trailing Stoploss: 5
Forcesell Trades: 10
Sell Signal Trades: 25

Another recommendation that would be nice to add is an Average Duration stat box because there is nothing more worrisome than wondering if the trade is going to win, lose, or draw. Having an Average Duration for telegram trades can make the user feel comfortable knowing how long a winning or losing trade should take based on the stats.

/duration

Avg. Duration Winners: 1:55:00
Avg. Duration Losers: 6 days, 21:07:00
Avg. Duration Draws: 4 days, 09:05:00

Hints from issue discussion:
well ideally, you should know this out of backtesting.

I can understand why it might seem tempting to have this - but you'll be mostly interrested in this during the first few hours / days / trades of a new strategy - when this information is not available / not yet reliable.

After running the strategy for a few weeks / a month (only then it'll have any statistical significance) - you'll most likely already have a feel for how the strategy behaves by that point - so you'll not really take a look at it anymore - at least that's my feeling.

---

I guess this can be a nice entrypoint for people starting to contribute - as it'll need only a limited view into the bot's structure.
Hi, Can I take this one? It sounds interesting to me. Thank you.
@imxuwang absolutely, thanks for your interrest!

let me know (best via slack?) if you need any help / guidance.
as a hint: you'll most likely be looking at the `telegram.py` and `rpc.py` files ...
 
Yeah, that would be cool. It doesn't seem to hard to add this in the telegram and rpc files. I had got started on the ROI trade count this morning part but I'm only 5 months into programming so it took me awhile. 
@xmatthias Yes, we can do it via slack. I will read these two scripts this evening.
I'm just not sure of having this as 2 seperate commands.
Maybe a `/stats` command would make more sense - to which we can add more statistics at a later point too.
> I'm just not sure of having this as 2 seperate commands.
> Maybe a `/stats` command would make more sense - to which we can add more statistics at a later point too.

+1 to `/stats`.
@xmatthias How do I use slack or other applications to discuss general questions? Thank you.
well you should use the [slack invite link](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) we have on the readme.md page.

for development related things, i'd recommend the #dev channel ... ;) but there's activity in other channels as well

My current solution is to add the `/stats` command in the Telegram client. The command will return sell reason stats and summary metrics (similar to the ones in backtesting output).
- From `telegram.py` `_trades` method I can get the latest trades.
- The latest trades come from Trade table (`presistence.py`), which contains:
  - open_timestamp
  - close_timestamp
  - sell_reason
- To keep stats consistent, I prefer to use the logic in backtesting to generate sell summary and summary metrics.

I found `freqtrade/optimize/backtesting.py` but did not find where the summary calculation code is. Any hint on this? Thank you.

Commit message: Summarize trade reason for telegram command /stats.

Patch to review:
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index a01efaed6a7..47e9d67dc18 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -108,6 +108,7 @@ def _init(self) -> None:
             CommandHandler('edge', self._edge),
             CommandHandler('help', self._help),
             CommandHandler('version', self._version),
+            CommandHandler('stats', self._stats),
         ]
         for handle in handles:
             self._updater.dispatcher.add_handler(handle)
@@ -738,6 +739,50 @@ def _version(self, update: Update, context: CallbackContext) -> None:
         """
         self._send_msg('*Version:* `{}`'.format(__version__))
 
+    @authorized_only
+    def _stats(self, update: Update, context: CallbackContext) -> None:
+        """
+        Handler for /stats
+        https://github.com/freqtrade/freqtrade/issues/3783
+        Show stats of recent trades
+        :param update: message update
+        :return: None
+        """
+        # TODO: self._send_msg(...)
+        def trade_win_loss(trade):
+            if trade['profit_abs'] > 0:
+                return 'Wins'
+            elif trade['profit_abs'] < 0:
+                return 'Losses'
+            else:
+                return 'Draws'
+
+        trades = self._rpc_trade_history(-1)
+        trades_closed = [trade for trade in trades if not trade['is_open']]
+
+        # Sell reason
+        sell_reasons = {}
+        for trade in trades_closed:
+            if trade['sell_reason'] in sell_reasons:
+                sell_reasons[trade['sell_reason']][trade_win_loss(trade)] += 1
+            else:
+            win_loss_count = {'Wins': 0, 'Losses': 0, 'Draws': 0}
+            win_loss_count[trade_win_loss(trade)] += 1
+            sell_reasons[trade['sell_reason']] = win_loss_count
+        sell_reason_msg = [
+            '| Sell Reason | Sells | Wins | Draws | Losses |',
+            '|-------------|------:|-----:|------:|-------:|'
+        ]
+        # | Sell Reason | Sells | Wins | Draws | Losses |
+        # |-------------|------:|-----:|------:|-------:|
+        # | test        |     1 |    2 |     3 |      4 |
+        for reason, count in sell_reasons.items():
+            msg = f'| `{reason}` | `{sum(count.values())}` | `{count['Wins']}` | `{count['Draws']}` | `{count['Losses']}` |'
+            sell_reason_msg.append(msg)
+
+        # TODO: Duration
+
+        
     @authorized_only
     def _show_config(self, update: Update, context: CallbackContext) -> None:
         """


Code review comments:
diff_hunk: @@ -738,6 +739,50 @@ def _version(self, update: Update, context: CallbackContext) -> None:
         """
         self._send_msg('*Version:* `{}`'.format(__version__))
 
+    @authorized_only
+    def _stats(self, update: Update, context: CallbackContext) -> None:
+        """
+        Handler for /stats
+        https://github.com/freqtrade/freqtrade/issues/3783
+        Show stats of recent trades
+        :param update: message update
+        :return: None
+        """
+        # TODO: self._send_msg(...)
+        def trade_win_loss(trade):
+            if trade['profit_abs'] > 0:
+                return 'Wins'
+            elif trade['profit_abs'] < 0:
+                return 'Losses'
+            else:
+                return 'Draws'
+
+        trades = self._rpc_trade_history(-1)
+        trades_closed = [trade for trade in trades if not trade['is_open']]
+
+        # Sell reason
+        sell_reasons = {}
+        for trade in trades_closed:
+            if trade['sell_reason'] in sell_reasons:
+                sell_reasons[trade['sell_reason']][trade_win_loss(trade)] += 1
+            else:
+            win_loss_count = {'Wins': 0, 'Losses': 0, 'Draws': 0}
+            win_loss_count[trade_win_loss(trade)] += 1
+            sell_reasons[trade['sell_reason']] = win_loss_count
+        sell_reason_msg = [
+            '| Sell Reason | Sells | Wins | Draws | Losses |',
+            '|-------------|------:|-----:|------:|-------:|'
path: freqtrade/rpc/telegram.py
text: @user1:
You should be able to use `tabulate()` for this.

It'll do a much better job at formatting a table than we could... (we're using it for `_status_table()` for example).