From 05046b9eef0ff92e74fb48e1e9e90f6b6551d52f Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sat, 22 Jan 2022 06:54:49 +0000 Subject: [PATCH 1/6] Add more info on status message --- freqtrade/persistence/models.py | 15 ++++++++ freqtrade/rpc/rpc.py | 4 ++- freqtrade/rpc/telegram.py | 63 +++++++++++++++++++++++++++++++-- tests/rpc/test_rpc.py | 6 ++++ tests/test_persistence.py | 2 ++ 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 98a5329ba..275a2f949 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -282,6 +282,20 @@ class LocalTrade(): return self.close_date.replace(tzinfo=timezone.utc) def to_json(self) -> Dict[str, Any]: + fill_buy = self.select_filled_orders('buy') + buys_json = dict() + if len(fill_buy) > 0: + for x in range(len(fill_buy)): + buy = dict( + cost=fill_buy[x].cost if fill_buy[x].cost else 0.0, + amount=fill_buy[x].amount, + price=fill_buy[x].price, + average=round(fill_buy[x].average, 8) if fill_buy[x].average else 0.0, + order_filled_date=fill_buy[x].order_filled_date.strftime(DATETIME_PRINT_FORMAT) + if fill_buy[x].order_filled_date else None + ) + buys_json[str(x)] = buy + return { 'trade_id': self.id, 'pair': self.pair, @@ -345,6 +359,7 @@ class LocalTrade(): 'max_rate': self.max_rate, 'open_order_id': self.open_order_id, + 'filled_buys': buys_json, } @staticmethod diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 41fd37e51..bd6373cce 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -208,6 +208,8 @@ class RPC: order['type'], order['side'], order['remaining'] ) if order else None, )) + cp_cfg = self._config + trade_dict['position_adjustment_enable'] = cp_cfg['position_adjustment_enable'] results.append(trade_dict) return results @@ -242,7 +244,7 @@ class RPC: trade.id, trade.pair + ('*' if (trade.open_order_id is not None and trade.close_rate_requested is None) else '') - + ('**' if (trade.close_rate_requested is not None) else ''), + + ('**' if (trade.close_rate_requested is not None) else ''), shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), profit_str ] diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 716694a81..03eb836fa 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -369,6 +369,47 @@ class Telegram(RPCHandler): else: return "\N{CROSS MARK}" + def _prepare_buy_details(self, filled_trades, base_currency): + """ + Prepare details of trade with buy adjustment enabled + """ + lines = [] + for x in range(len(filled_trades)): + cur_buy_date = arrow.get(filled_trades[str(x)]["order_filled_date"]) + cur_buy_amount = filled_trades[str(x)]["amount"] + cur_buy_average = filled_trades[str(x)]["average"] + lines.append(" ") + if x == 0: + lines.append("*Buy #{}:*".format(x+1)) + lines.append("*Buy Amount:* {} ({:.8f} {})" + .format(cur_buy_amount, filled_trades[str(x)]["cost"], base_currency)) + lines.append("*Average Buy Price:* {}".format(cur_buy_average)) + else: + sumA = 0 + sumB = 0 + for y in range(x): + sumA += (filled_trades[str(y)]["amount"] * filled_trades[str(y)]["average"]) + sumB += filled_trades[str(y)]["amount"] + prev_avg_price = sumA/sumB + price_to_1st_buy = (cur_buy_average - filled_trades["0"]["average"]) \ + / filled_trades["0"]["average"] + minus_on_buy = (cur_buy_average - prev_avg_price)/prev_avg_price + dur_buys = cur_buy_date - arrow.get(filled_trades[str(x-1)]["order_filled_date"]) + days = dur_buys.days + hours, remainder = divmod(dur_buys.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + lines.append("*Buy #{}:* at {:.2%} avg profit".format(x+1, minus_on_buy)) + lines.append("({})".format(cur_buy_date + .humanize(granularity=["day", "hour", "minute"]))) + lines.append("*Buy Amount:* {} ({:.8f} {})" + .format(cur_buy_amount, filled_trades[str(x)]["cost"], base_currency)) + lines.append("*Average Buy Price:* {} ({:.2%} from 1st buy rate)" + .format(cur_buy_average, price_to_1st_buy)) + lines.append("*Filled at:* {}".format(filled_trades[str(x)]["order_filled_date"])) + lines.append("({}d {}h {}m {}s from previous buy)" + .format(days, hours, minutes, seconds)) + return lines + @authorized_only def _status(self, update: Update, context: CallbackContext) -> None: """ @@ -396,17 +437,31 @@ class Telegram(RPCHandler): messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() + r['filled_buys'] = r.get('filled_buys', []) + r['num_buys'] = len(r['filled_buys']) + r['sell_reason'] = r.get('sell_reason', "") + r['position_adjustment_enable'] = r.get('position_adjustment_enable', False) lines = [ "*Trade ID:* `{trade_id}` `(since {open_date_hum})`", "*Current Pair:* {pair}", "*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "", + "*Sell Reason:* `{sell_reason}`" if r['sell_reason'] else "", + ] + + if r['position_adjustment_enable']: + lines.append("*Number of Buy(s):* `{num_buys}`") + + lines.extend([ "*Open Rate:* `{open_rate:.8f}`", - "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", + "*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "", + "*Open Date:* `{open_date}`", + "*Close Date:* `{close_date}`" if r['close_date'] else "", "*Current Rate:* `{current_rate:.8f}`", ("*Current Profit:* " if r['is_open'] else "*Close Profit: *") + "`{profit_ratio:.2%}`", - ] + ]) + if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] and r['initial_stop_loss_ratio'] is not None): # Adding initial stoploss only if it is different from stoploss @@ -424,6 +479,10 @@ class Telegram(RPCHandler): else: lines.append("*Open Order:* `{open_order}`") + if len(r['filled_buys']) > 1: + lines_detail = self._prepare_buy_details(r['filled_buys'], r['base_currency']) + lines.extend(lines_detail) + # Filter empty lines using list-comprehension messages.append("\n".join([line for line in lines if line]).format(**r)) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e86022a91..0ea147c0a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -108,6 +108,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', + 'position_adjustment_enable': False, + 'filled_buys': {'0': {'amount': 91.07468123, 'average': 1.098e-05, + 'cost': 0.0009999999999054, 'order_filled_date': ANY, 'price': 1.098e-05}}, } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -175,6 +178,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', + 'position_adjustment_enable': False, + 'filled_buys': {'0': {'amount': 91.07468123, 'average': 1.098e-05, + 'cost': 0.0009999999999054, 'order_filled_date': ANY, 'price': 1.098e-05}}, } diff --git a/tests/test_persistence.py b/tests/test_persistence.py index d98238f6f..d6b12ea8c 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -903,6 +903,7 @@ def test_to_json(default_conf, fee): 'buy_tag': None, 'timeframe': None, 'exchange': 'binance', + 'filled_buys': {} } # Simulate dry_run entries @@ -970,6 +971,7 @@ def test_to_json(default_conf, fee): 'buy_tag': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', + 'filled_buys': {} } From 480ed90a02b1b05e765096364af876569589bb17 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 23 Jan 2022 11:33:06 +0000 Subject: [PATCH 2/6] create to_json function for Order --- freqtrade/persistence/models.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 275a2f949..14ac65e61 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -165,6 +165,16 @@ class Order(_DECL_BASE): self.order_filled_date = datetime.now(timezone.utc) self.order_update_date = datetime.now(timezone.utc) + def to_json(self) -> Dict[str, Any]: + return { + 'cost': self.cost if self.cost else 0, + 'amount': self.amount, + 'price': self.price, + 'average': round(self.average, 8) if self.average else 0, + 'order_filled_date': self.order_filled_date.strftime(DATETIME_PRINT_FORMAT) + if self.order_filled_date else None + } + @staticmethod def update_orders(orders: List['Order'], order: Dict[str, Any]): """ @@ -286,15 +296,7 @@ class LocalTrade(): buys_json = dict() if len(fill_buy) > 0: for x in range(len(fill_buy)): - buy = dict( - cost=fill_buy[x].cost if fill_buy[x].cost else 0.0, - amount=fill_buy[x].amount, - price=fill_buy[x].price, - average=round(fill_buy[x].average, 8) if fill_buy[x].average else 0.0, - order_filled_date=fill_buy[x].order_filled_date.strftime(DATETIME_PRINT_FORMAT) - if fill_buy[x].order_filled_date else None - ) - buys_json[str(x)] = buy + buys_json[str(x)] = fill_buy[x].to_json() return { 'trade_id': self.id, From 1f26709aca80df164e6952ceaa586d6ed98cb20c Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Wed, 26 Jan 2022 07:06:52 +0000 Subject: [PATCH 3/6] changes --- freqtrade/persistence/models.py | 42 +++++++++++++++++++++++---------- freqtrade/rpc/telegram.py | 25 ++++++++++---------- tests/rpc/test_rpc.py | 16 +++++++++---- tests/rpc/test_rpc_telegram.py | 3 ++- tests/test_persistence.py | 6 +++-- 5 files changed, 59 insertions(+), 33 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 14ac65e61..340aad331 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -167,12 +167,23 @@ class Order(_DECL_BASE): def to_json(self) -> Dict[str, Any]: return { - 'cost': self.cost if self.cost else 0, 'amount': self.amount, - 'price': self.price, 'average': round(self.average, 8) if self.average else 0, + 'cost': self.cost if self.cost else 0, + 'filled': self.filled, + 'ft_order_side': self.ft_order_side, + 'order_date': self.order_date.strftime(DATETIME_PRINT_FORMAT) + if self.order_date else None, + 'order_timestamp': int(self.order_date.replace( + tzinfo=timezone.utc).timestamp() * 1000) if self.order_date else None, 'order_filled_date': self.order_filled_date.strftime(DATETIME_PRINT_FORMAT) - if self.order_filled_date else None + if self.order_filled_date else None, + 'order_filled_timestamp': int(self.order_filled_date.replace( + tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, + 'order_type': self.order_type, + 'price': self.price, + 'remaining': self.remaining, + 'status': self.status, } @staticmethod @@ -292,11 +303,15 @@ class LocalTrade(): return self.close_date.replace(tzinfo=timezone.utc) def to_json(self) -> Dict[str, Any]: - fill_buy = self.select_filled_orders('buy') - buys_json = dict() - if len(fill_buy) > 0: - for x in range(len(fill_buy)): - buys_json[str(x)] = fill_buy[x].to_json() + filled_orders = self.select_filled_orders() + filled_buys = [] + filled_sells = [] + if len(filled_orders) > 0: + for x in range(len(filled_orders)): + if filled_orders[x].ft_order_side == 'buy': + filled_buys.append(filled_orders[x].to_json()) + elif filled_orders[x].ft_order_side == 'sell': + filled_sells.append(filled_orders[x].to_json()) return { 'trade_id': self.id, @@ -361,7 +376,8 @@ class LocalTrade(): 'max_rate': self.max_rate, 'open_order_id': self.open_order_id, - 'filled_buys': buys_json, + 'filled_buys': filled_buys, + 'filled_sells': filled_sells, } @staticmethod @@ -631,14 +647,14 @@ class LocalTrade(): else: return None - def select_filled_orders(self, order_side: str) -> List['Order']: + def select_filled_orders(self, order_side: Optional[str] = None) -> List['Order']: """ Finds filled orders for this orderside. - :param order_side: Side of the order (either 'buy' or 'sell') + :param order_side: Side of the order (either 'buy', 'sell', or None) :return: array of Order objects """ - return [o for o in self.orders if o.ft_order_side == order_side and - o.ft_is_open is False and + return [o for o in self.orders if ((o.ft_order_side == order_side) or (order_side is None)) + and o.ft_is_open is False and (o.filled or 0) > 0 and o.status in NON_OPEN_EXCHANGE_STATES] diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 03eb836fa..7f8c3fb1a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -375,37 +375,37 @@ class Telegram(RPCHandler): """ lines = [] for x in range(len(filled_trades)): - cur_buy_date = arrow.get(filled_trades[str(x)]["order_filled_date"]) - cur_buy_amount = filled_trades[str(x)]["amount"] - cur_buy_average = filled_trades[str(x)]["average"] + current_buy_datetime = arrow.get(filled_trades[x]["order_filled_date"]) + cur_buy_amount = filled_trades[x]["amount"] + cur_buy_average = filled_trades[x]["average"] lines.append(" ") if x == 0: lines.append("*Buy #{}:*".format(x+1)) lines.append("*Buy Amount:* {} ({:.8f} {})" - .format(cur_buy_amount, filled_trades[str(x)]["cost"], base_currency)) + .format(cur_buy_amount, filled_trades[x]["cost"], base_currency)) lines.append("*Average Buy Price:* {}".format(cur_buy_average)) else: sumA = 0 sumB = 0 for y in range(x): - sumA += (filled_trades[str(y)]["amount"] * filled_trades[str(y)]["average"]) - sumB += filled_trades[str(y)]["amount"] + sumA += (filled_trades[y]["amount"] * filled_trades[y]["average"]) + sumB += filled_trades[y]["amount"] prev_avg_price = sumA/sumB - price_to_1st_buy = (cur_buy_average - filled_trades["0"]["average"]) \ - / filled_trades["0"]["average"] + price_to_1st_buy = (cur_buy_average - filled_trades[0]["average"]) \ + / filled_trades[0]["average"] minus_on_buy = (cur_buy_average - prev_avg_price)/prev_avg_price - dur_buys = cur_buy_date - arrow.get(filled_trades[str(x-1)]["order_filled_date"]) + dur_buys = current_buy_datetime - arrow.get(filled_trades[x-1]["order_filled_date"]) days = dur_buys.days hours, remainder = divmod(dur_buys.seconds, 3600) minutes, seconds = divmod(remainder, 60) lines.append("*Buy #{}:* at {:.2%} avg profit".format(x+1, minus_on_buy)) - lines.append("({})".format(cur_buy_date + lines.append("({})".format(current_buy_datetime .humanize(granularity=["day", "hour", "minute"]))) lines.append("*Buy Amount:* {} ({:.8f} {})" - .format(cur_buy_amount, filled_trades[str(x)]["cost"], base_currency)) + .format(cur_buy_amount, filled_trades[x]["cost"], base_currency)) lines.append("*Average Buy Price:* {} ({:.2%} from 1st buy rate)" .format(cur_buy_average, price_to_1st_buy)) - lines.append("*Filled at:* {}".format(filled_trades[str(x)]["order_filled_date"])) + lines.append("*Order filled at:* {}".format(filled_trades[x]["order_filled_date"])) lines.append("({}d {}h {}m {}s from previous buy)" .format(days, hours, minutes, seconds)) return lines @@ -437,7 +437,6 @@ class Telegram(RPCHandler): messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() - r['filled_buys'] = r.get('filled_buys', []) r['num_buys'] = len(r['filled_buys']) r['sell_reason'] = r.get('sell_reason', "") r['position_adjustment_enable'] = r.get('position_adjustment_enable', False) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index a91340e39..2a1964732 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -109,8 +109,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_order': None, 'exchange': 'binance', 'position_adjustment_enable': False, - 'filled_buys': {'0': {'amount': 91.07468123, 'average': 1.098e-05, - 'cost': 0.0009999999999054, 'order_filled_date': ANY, 'price': 1.098e-05}}, + 'filled_buys': [{'amount': 91.07468123, 'average': 1.098e-05, + 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', + 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, + 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, + 'remaining': ANY, 'status': ANY}], + 'filled_sells': [] } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -179,8 +183,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_order': None, 'exchange': 'binance', 'position_adjustment_enable': False, - 'filled_buys': {'0': {'amount': 91.07468123, 'average': 1.098e-05, - 'cost': 0.0009999999999054, 'order_filled_date': ANY, 'price': 1.098e-05}}, + 'filled_buys': [{'amount': 91.07468123, 'average': 1.098e-05, + 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', + 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, + 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, + 'remaining': ANY, 'status': ANY}], + 'filled_sells': [] } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 1d638eed1..600568580 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -201,7 +201,8 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'stoploss_current_dist_ratio': -0.0002, 'stop_loss_ratio': -0.0001, 'open_order': '(limit buy rem=0.00000000)', - 'is_open': True + 'is_open': True, + 'filled_buys': [] }]), ) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index d6b12ea8c..1691af820 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -903,7 +903,8 @@ def test_to_json(default_conf, fee): 'buy_tag': None, 'timeframe': None, 'exchange': 'binance', - 'filled_buys': {} + 'filled_buys': [], + 'filled_sells': [] } # Simulate dry_run entries @@ -971,7 +972,8 @@ def test_to_json(default_conf, fee): 'buy_tag': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', - 'filled_buys': {} + 'filled_buys': [], + 'filled_sells': [] } From 29879bb415125162874cf97da761f6ab80e884ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Feb 2022 19:11:35 +0100 Subject: [PATCH 4/6] Update wording to entry/exit --- freqtrade/persistence/models.py | 20 +++++++++++--------- freqtrade/rpc/telegram.py | 7 ++++--- tests/rpc/test_rpc.py | 28 ++++++++++++++++------------ tests/rpc/test_rpc_telegram.py | 2 +- tests/test_persistence.py | 8 ++++---- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a78eff6af..319a8749a 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -172,6 +172,7 @@ class Order(_DECL_BASE): 'cost': self.cost if self.cost else 0, 'filled': self.filled, 'ft_order_side': self.ft_order_side, + 'is_open': self.ft_is_open, 'order_date': self.order_date.strftime(DATETIME_PRINT_FORMAT) if self.order_date else None, 'order_timestamp': int(self.order_date.replace( @@ -181,6 +182,7 @@ class Order(_DECL_BASE): 'order_filled_timestamp': int(self.order_filled_date.replace( tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, 'order_type': self.order_type, + 'pair': self.ft_pair, 'price': self.price, 'remaining': self.remaining, 'status': self.status, @@ -304,14 +306,14 @@ class LocalTrade(): def to_json(self) -> Dict[str, Any]: filled_orders = self.select_filled_orders() - filled_buys = [] - filled_sells = [] + filled_entries = [] + filled_exits = [] if len(filled_orders) > 0: - for x in range(len(filled_orders)): - if filled_orders[x].ft_order_side == 'buy': - filled_buys.append(filled_orders[x].to_json()) - elif filled_orders[x].ft_order_side == 'sell': - filled_sells.append(filled_orders[x].to_json()) + for order in filled_orders: + if order.ft_order_side == 'buy': + filled_entries.append(order.to_json()) + if order.ft_order_side == 'sell': + filled_exits.append(order.to_json()) return { 'trade_id': self.id, @@ -376,8 +378,8 @@ class LocalTrade(): 'max_rate': self.max_rate, 'open_order_id': self.open_order_id, - 'filled_buys': filled_buys, - 'filled_sells': filled_sells, + 'filled_entry_orders': filled_entries, + 'filled_exit_orders': filled_exits, } @staticmethod diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 4943f9df2..d4d6ae7af 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -437,7 +437,7 @@ class Telegram(RPCHandler): messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() - r['num_buys'] = len(r['filled_buys']) + r['num_entries'] = len(r['filled_entry_orders']) r['sell_reason'] = r.get('sell_reason', "") r['position_adjustment_enable'] = r.get('position_adjustment_enable', False) lines = [ @@ -478,8 +478,9 @@ class Telegram(RPCHandler): else: lines.append("*Open Order:* `{open_order}`") - if len(r['filled_buys']) > 1: - lines_detail = self._prepare_buy_details(r['filled_buys'], r['base_currency']) + if len(r['filled_entry_orders']) > 1: + lines_detail = self._prepare_buy_details( + r['filled_entry_orders'], r['base_currency']) lines.extend(lines_detail) # Filter empty lines using list-comprehension diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 7e3334c46..1cdb0e4e8 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -109,12 +109,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_order': None, 'exchange': 'binance', 'position_adjustment_enable': False, - 'filled_buys': [{'amount': 91.07468123, 'average': 1.098e-05, - 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', - 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, - 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, - 'remaining': ANY, 'status': ANY}], - 'filled_sells': [] + 'filled_entry_orders': [{ + 'amount': 91.07468123, 'average': 1.098e-05, + 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', + 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, + 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, + 'is_open': False, 'pair': 'ETH/BTC', + 'remaining': ANY, 'status': ANY}], + 'filled_exit_orders': [] } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -183,12 +185,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'open_order': None, 'exchange': 'binance', 'position_adjustment_enable': False, - 'filled_buys': [{'amount': 91.07468123, 'average': 1.098e-05, - 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', - 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, - 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, - 'remaining': ANY, 'status': ANY}], - 'filled_sells': [] + 'filled_entry_orders': [{ + 'amount': 91.07468123, 'average': 1.098e-05, + 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', + 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, + 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, + 'is_open': False, 'pair': 'ETH/BTC', + 'remaining': ANY, 'status': ANY}], + 'filled_exit_orders': [] } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 600568580..13ec3f316 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -202,7 +202,7 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'stop_loss_ratio': -0.0001, 'open_order': '(limit buy rem=0.00000000)', 'is_open': True, - 'filled_buys': [] + 'filled_entry_orders': [] }]), ) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index f86d9605c..d2cb91d5e 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -903,8 +903,8 @@ def test_to_json(default_conf, fee): 'buy_tag': None, 'timeframe': None, 'exchange': 'binance', - 'filled_buys': [], - 'filled_sells': [] + 'filled_entry_orders': [], + 'filled_exit_orders': [] } # Simulate dry_run entries @@ -972,8 +972,8 @@ def test_to_json(default_conf, fee): 'buy_tag': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', - 'filled_buys': [], - 'filled_sells': [] + 'filled_entry_orders': [], + 'filled_exit_orders': [] } From 1e6362debffd8ca0a2a84c93d98ed7f65889cd89 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Feb 2022 19:41:45 +0100 Subject: [PATCH 5/6] Add test for new /status telegram message --- freqtrade/rpc/rpc.py | 2 -- freqtrade/rpc/telegram.py | 11 ++++---- tests/conftest_trades.py | 1 + tests/rpc/test_rpc.py | 2 -- tests/rpc/test_rpc_telegram.py | 47 ++++++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index d731f6fa4..ed41dbb01 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -213,8 +213,6 @@ class RPC: order['type'], order['side'], order['remaining'] ) if order else None, )) - cp_cfg = self._config - trade_dict['position_adjustment_enable'] = cp_cfg['position_adjustment_enable'] results.append(trade_dict) return results diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d4d6ae7af..ba6d8c75a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -391,8 +391,8 @@ class Telegram(RPCHandler): sumA += (filled_trades[y]["amount"] * filled_trades[y]["average"]) sumB += filled_trades[y]["amount"] prev_avg_price = sumA/sumB - price_to_1st_buy = (cur_buy_average - filled_trades[0]["average"]) \ - / filled_trades[0]["average"] + price_to_1st_buy = ((cur_buy_average - filled_trades[0]["average"]) + / filled_trades[0]["average"]) minus_on_buy = (cur_buy_average - prev_avg_price)/prev_avg_price dur_buys = current_buy_datetime - arrow.get(filled_trades[x-1]["order_filled_date"]) days = dur_buys.days @@ -433,13 +433,12 @@ class Telegram(RPCHandler): trade_ids = [int(i) for i in context.args if i.isnumeric()] results = self._rpc._rpc_trade_status(trade_ids=trade_ids) - + position_adjust = self._config.get('position_adjustment_enable', False) messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() r['num_entries'] = len(r['filled_entry_orders']) r['sell_reason'] = r.get('sell_reason', "") - r['position_adjustment_enable'] = r.get('position_adjustment_enable', False) lines = [ "*Trade ID:* `{trade_id}` `(since {open_date_hum})`", "*Current Pair:* {pair}", @@ -448,8 +447,8 @@ class Telegram(RPCHandler): "*Sell Reason:* `{sell_reason}`" if r['sell_reason'] else "", ] - if r['position_adjustment_enable']: - lines.append("*Number of Buy(s):* `{num_buys}`") + if position_adjust: + lines.append("*Number of Buy(s):* `{num_entries}`") lines.extend([ "*Open Rate:* `{open_rate:.8f}`", diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 4496df37d..70a2a99a2 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -14,6 +14,7 @@ def mock_order_1(): 'side': 'buy', 'type': 'limit', 'price': 0.123, + 'average': 0.123, 'amount': 123.0, 'filled': 123.0, 'remaining': 0.0, diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 1cdb0e4e8..05e9c1da8 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -108,7 +108,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', - 'position_adjustment_enable': False, 'filled_entry_orders': [{ 'amount': 91.07468123, 'average': 1.098e-05, 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', @@ -184,7 +183,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', - 'position_adjustment_enable': False, 'filled_entry_orders': [{ 'amount': 91.07468123, 'average': 1.098e-05, 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 13ec3f316..afa7a6a67 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -23,6 +23,7 @@ from freqtrade.exceptions import OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.loggers import setup_logging from freqtrade.persistence import PairLocks, Trade +from freqtrade.persistence.models import Order from freqtrade.rpc import RPC from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.telegram import Telegram, authorized_only @@ -218,6 +219,52 @@ def test_telegram_status(default_conf, update, mocker) -> None: assert status_table.call_count == 1 +@pytest.mark.usefixtures("init_persistence") +def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: + update.message.chat.id = "123" + default_conf['telegram']['enabled'] = False + default_conf['telegram']['chat_id'] = "123" + default_conf['position_adjustment_enable'] = True + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_order=MagicMock(return_value=None), + get_rate=MagicMock(return_value=0.22), + ) + + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + + create_mock_trades(fee) + trades = Trade.get_open_trades() + trade = trades[0] + trade.orders.append(Order( + order_id='5412vbb', + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=trade.open_rate * 0.95, + average=trade.open_rate * 0.95, + filled=trade.amount, + remaining=0, + cost=trade.amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + ) + trade.recalc_trade_from_orders() + Trade.commit() + + telegram._status(update=update, context=MagicMock()) + assert msg_mock.call_count == 4 + msg = msg_mock.call_args_list[0][0][0] + assert re.search(r'Number of Buy.*2', msg) + assert re.search(r'Average Buy Price', msg) + assert re.search(r'Order filled at', msg) + + def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 3 mocker.patch.multiple( From f8faf748df2d36ef97bfb327b3885423cb385b23 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Feb 2022 19:47:03 +0100 Subject: [PATCH 6/6] Simplify prepare_buy_details --- freqtrade/rpc/telegram.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ba6d8c75a..857aec3d6 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -369,32 +369,32 @@ class Telegram(RPCHandler): else: return "\N{CROSS MARK}" - def _prepare_buy_details(self, filled_trades, base_currency): + def _prepare_buy_details(self, filled_orders, base_currency): """ Prepare details of trade with buy adjustment enabled """ lines = [] - for x in range(len(filled_trades)): - current_buy_datetime = arrow.get(filled_trades[x]["order_filled_date"]) - cur_buy_amount = filled_trades[x]["amount"] - cur_buy_average = filled_trades[x]["average"] + for x, order in enumerate(filled_orders): + current_buy_datetime = arrow.get(order["order_filled_date"]) + cur_buy_amount = order["amount"] + cur_buy_average = order["average"] lines.append(" ") if x == 0: lines.append("*Buy #{}:*".format(x+1)) lines.append("*Buy Amount:* {} ({:.8f} {})" - .format(cur_buy_amount, filled_trades[x]["cost"], base_currency)) + .format(cur_buy_amount, order["cost"], base_currency)) lines.append("*Average Buy Price:* {}".format(cur_buy_average)) else: sumA = 0 sumB = 0 for y in range(x): - sumA += (filled_trades[y]["amount"] * filled_trades[y]["average"]) - sumB += filled_trades[y]["amount"] + sumA += (filled_orders[y]["amount"] * filled_orders[y]["average"]) + sumB += filled_orders[y]["amount"] prev_avg_price = sumA/sumB - price_to_1st_buy = ((cur_buy_average - filled_trades[0]["average"]) - / filled_trades[0]["average"]) + price_to_1st_buy = ((cur_buy_average - filled_orders[0]["average"]) + / filled_orders[0]["average"]) minus_on_buy = (cur_buy_average - prev_avg_price)/prev_avg_price - dur_buys = current_buy_datetime - arrow.get(filled_trades[x-1]["order_filled_date"]) + dur_buys = current_buy_datetime - arrow.get(filled_orders[x-1]["order_filled_date"]) days = dur_buys.days hours, remainder = divmod(dur_buys.seconds, 3600) minutes, seconds = divmod(remainder, 60) @@ -402,10 +402,10 @@ class Telegram(RPCHandler): lines.append("({})".format(current_buy_datetime .humanize(granularity=["day", "hour", "minute"]))) lines.append("*Buy Amount:* {} ({:.8f} {})" - .format(cur_buy_amount, filled_trades[x]["cost"], base_currency)) + .format(cur_buy_amount, order["cost"], base_currency)) lines.append("*Average Buy Price:* {} ({:.2%} from 1st buy rate)" .format(cur_buy_average, price_to_1st_buy)) - lines.append("*Order filled at:* {}".format(filled_trades[x]["order_filled_date"])) + lines.append("*Order filled at:* {}".format(order["order_filled_date"])) lines.append("({}d {}h {}m {}s from previous buy)" .format(days, hours, minutes, seconds)) return lines