Skip to content

Commit 571497e

Browse files
authored
Merge pull request freqtrade#11974 from qqqqqf-q/feat/telegram-profit-direction
feat(telegram): Add /profit long and /profit short commands
2 parents 35dab9b + d710c85 commit 571497e

File tree

5 files changed

+336
-104
lines changed

5 files changed

+336
-104
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ Telegram is not mandatory. However, this is a great way to control your bot. Mor
146146
- `/stopentry`: Stop entering new trades.
147147
- `/status <trade_id>|[table]`: Lists all or specific open trades.
148148
- `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days.
149+
- `/profit_long [<n>]`: Lists cumulative profit from all finished long trades, over the last n days.
150+
- `/profit_short [<n>]`: Lists cumulative profit from all finished short trades, over the last n days.
149151
- `/forceexit <trade_id>|all`: Instantly exits the given trade (Ignoring `minimum_roi`).
150152
- `/fx <trade_id>|all`: Alias to `/forceexit`
151153
- `/performance`: Show performance of each finished trade grouped by pair
@@ -154,6 +156,7 @@ Telegram is not mandatory. However, this is a great way to control your bot. Mor
154156
- `/help`: Show help message.
155157
- `/version`: Show version.
156158

159+
157160
## Development branches
158161

159162
The project is currently setup in two main branches:

freqtrade/persistence/trade_model.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,32 +2090,34 @@ def get_mix_tag_performance(pair: str | None) -> list[dict[str, Any]]:
20902090
return resp
20912091

20922092
@staticmethod
2093-
def get_best_pair(start_date: datetime | None = None):
2093+
def get_best_pair(trade_filter: list | None = None):
20942094
"""
20952095
Get best pair with closed trade.
20962096
NOTE: Not supported in Backtesting.
20972097
:returns: Tuple containing (pair, profit_sum)
20982098
"""
2099-
filters: list = [Trade.is_open.is_(False)]
2100-
if start_date:
2101-
filters.append(Trade.close_date >= start_date)
2099+
if not trade_filter:
2100+
trade_filter = []
2101+
trade_filter.append(Trade.is_open.is_(False))
21022102

2103-
pair_rates_query = Trade._generic_performance_query([Trade.pair], filters)
2103+
pair_rates_query = Trade._generic_performance_query([Trade.pair], trade_filter)
21042104
best_pair = Trade.session.execute(pair_rates_query).first()
21052105
# returns pair, profit_ratio, abs_profit, count
21062106
return best_pair
21072107

21082108
@staticmethod
2109-
def get_trading_volume(start_date: datetime | None = None) -> float:
2109+
def get_trading_volume(trade_filter: list | None = None) -> float:
21102110
"""
21112111
Get Trade volume based on Orders
21122112
NOTE: Not supported in Backtesting.
21132113
:returns: Tuple containing (pair, profit_sum)
21142114
"""
2115-
filters = [Order.status == "closed"]
2116-
if start_date:
2117-
filters.append(Order.order_filled_date >= start_date)
2115+
if not trade_filter:
2116+
trade_filter = []
2117+
trade_filter.append(Order.status == "closed")
21182118
trading_volume = Trade.session.execute(
2119-
select(func.sum(Order.cost).label("volume")).filter(*filters)
2119+
select(func.sum(Order.cost).label("volume"))
2120+
.join(Order._trade_live)
2121+
.filter(*trade_filter)
21202122
).scalar_one()
21212123
return trading_volume or 0.0

freqtrade/rpc/rpc.py

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from freqtrade.exchange.exchange_utils import price_to_precision
3535
from freqtrade.ft_types import AnnotationType
3636
from freqtrade.loggers import bufferHandler
37-
from freqtrade.persistence import CustomDataWrapper, KeyValueStore, PairLocks, Trade
37+
from freqtrade.persistence import CustomDataWrapper, KeyValueStore, Order, PairLocks, Trade
3838
from freqtrade.persistence.models import PairLock, custom_data_rpc_wrapper
3939
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
4040
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@@ -502,20 +502,13 @@ def trade_win_loss(trade):
502502
durations = {"wins": wins_dur, "draws": draws_dur, "losses": losses_dur}
503503
return {"exit_reasons": exit_reasons, "durations": durations}
504504

505-
def _rpc_trade_statistics(
506-
self, stake_currency: str, fiat_display_currency: str, start_date: datetime | None = None
505+
def _collect_trade_statistics_data(
506+
self,
507+
trades: Sequence["Trade"],
508+
stake_currency: str,
509+
fiat_display_currency: str,
507510
) -> dict[str, Any]:
508-
"""Returns cumulative profit statistics"""
509-
510-
start_date = datetime.fromtimestamp(0) if start_date is None else start_date
511-
512-
trade_filter = (
513-
Trade.is_open.is_(False) & (Trade.close_date >= start_date)
514-
) | Trade.is_open.is_(True)
515-
trades: Sequence[Trade] = Trade.session.scalars(
516-
Trade.get_trades_query(trade_filter, include_orders=False).order_by(Trade.id)
517-
).all()
518-
511+
"""Iterate trades, calculate various statistics, and return intermediate results."""
519512
profit_all_coin = []
520513
profit_all_ratio = []
521514
profit_closed_coin = []
@@ -544,7 +537,7 @@ def _rpc_trade_statistics(
544537
losing_trades += 1
545538
losing_profit += profit_abs
546539
else:
547-
# Get current rate
540+
# Get current rate for open trades
548541
if len(trade.select_filled_orders(trade.entry_side)) == 0:
549542
# Skip trades with no filled orders
550543
continue
@@ -558,17 +551,74 @@ def _rpc_trade_statistics(
558551
profit_abs = nan
559552
else:
560553
_profit = trade.calculate_profit(trade.close_rate or current_rate)
561-
562554
profit_ratio = _profit.profit_ratio
563555
profit_abs = _profit.total_profit
564556

565557
profit_all_coin.append(profit_abs)
566558
profit_all_ratio.append(profit_ratio)
567559

560+
return {
561+
"profit_all_coin": profit_all_coin,
562+
"profit_all_ratio": profit_all_ratio,
563+
"profit_closed_coin": profit_closed_coin,
564+
"profit_closed_ratio": profit_closed_ratio,
565+
"durations": durations,
566+
"winning_trades": winning_trades,
567+
"losing_trades": losing_trades,
568+
"winning_profit": winning_profit,
569+
"losing_profit": losing_profit,
570+
}
571+
572+
def _rpc_trade_statistics(
573+
self,
574+
stake_currency: str,
575+
fiat_display_currency: str,
576+
start_date: datetime | None = None,
577+
direction: str | None = None,
578+
) -> dict[str, Any]:
579+
"""
580+
Returns cumulative profit statistics, with optional direction filter (long/short)
581+
"""
582+
start_date = datetime.fromtimestamp(0) if start_date is None else start_date
583+
584+
trade_filter = (
585+
Trade.is_open.is_(False) & (Trade.close_date >= start_date)
586+
) | Trade.is_open.is_(True)
587+
588+
if direction == "long":
589+
dir_filter = Trade.is_short.is_(False)
590+
trade_filter = trade_filter & dir_filter
591+
elif direction == "short":
592+
dir_filter = Trade.is_short.is_(True)
593+
trade_filter = trade_filter & dir_filter
594+
595+
trades: Sequence[Trade] = Trade.session.scalars(
596+
Trade.get_trades_query(trade_filter, include_orders=False).order_by(Trade.id)
597+
).all()
598+
599+
stats = self._collect_trade_statistics_data(trades, stake_currency, fiat_display_currency)
600+
601+
profit_all_coin = stats["profit_all_coin"]
602+
profit_all_ratio = stats["profit_all_ratio"]
603+
profit_closed_coin = stats["profit_closed_coin"]
604+
profit_closed_ratio = stats["profit_closed_ratio"]
605+
durations = stats["durations"]
606+
winning_trades = stats["winning_trades"]
607+
losing_trades = stats["losing_trades"]
608+
winning_profit = stats["winning_profit"]
609+
losing_profit = stats["losing_profit"]
610+
568611
closed_trade_count = len([t for t in trades if not t.is_open])
569612

570-
best_pair = Trade.get_best_pair(start_date)
571-
trading_volume = Trade.get_trading_volume(start_date)
613+
best_pair_filters = [Trade.close_date > start_date]
614+
trading_volume_filters = [Order.order_filled_date >= start_date]
615+
616+
if direction:
617+
best_pair_filters.append(dir_filter)
618+
trading_volume_filters.append(dir_filter)
619+
620+
best_pair = Trade.get_best_pair(best_pair_filters)
621+
trading_volume = Trade.get_trading_volume(trading_volume_filters)
572622

573623
# Prepare data to display
574624
profit_closed_coin_sum = round(sum(profit_closed_coin), 8)

0 commit comments

Comments
 (0)