diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index b6ed49755..0f10e43f1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -489,7 +489,7 @@ class FreqtradeBot(LoggingMixin): if amount > trade.amount: logger.info(f"Amount is higher than available. {amount} > {trade.amount}") return - self.execute_trade_exit(trade, current_rate, sell_reason=SellCheckTuple( + self.execute_trade_exit(trade, current_exit_rate, sell_reason=SellCheckTuple( sell_type=SellType.CUSTOM_SELL), sub_trade_amt=amount) def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: @@ -1409,9 +1409,6 @@ class FreqtradeBot(LoggingMixin): self.handle_order_fee(trade, order_obj, order) trade.update_trade(order_obj) - # TODO: is the below necessary? it's already done in update_trade for filled buys - trade.recalc_trade_from_orders() - Trade.commit() if order['status'] in constants.NON_OPEN_EXCHANGE_STATES: # If a buy order was closed, force update on stoploss on exchange diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index dbbfd67fb..d0559885b 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -180,7 +180,6 @@ class Order(_DECL_BASE): self.amount = order.get('amount', self.amount) self.filled = order.get('filled', self.filled) self.average = order.get('average', self.average) - self.initial_average = order.get('average', self.initial_average) self.remaining = order.get('remaining', self.remaining) self.cost = order.get('cost', self.cost) if 'timestamp' in order and order['timestamp'] is not None: @@ -516,46 +515,24 @@ class LocalTrade(): def process_sell_sub_trade(self, order: Order, is_closed: bool = True, is_non_bt: bool = True) -> None: - orders = (self.select_filled_orders('buy')) - sell_amount = order.safe_filled + sell_amount = order.safe_amount_after_fee sell_rate = order.safe_price sell_stake_amount = sell_rate * sell_amount * (1 - self.fee_close) - if is_closed: - if sell_amount == self.amount: + if sell_amount == self.amount: + if is_closed: self.close(sell_rate) if is_non_bt: Trade.commit() return - realized_profit = 0.0 - profit = 0.0 - idx = -1 - while sell_amount: - b_order = orders[idx] - buy_amount = b_order.safe_amount_after_fee - avg_rate = b_order.safe_price - buy_rate = b_order.initial_average or b_order.price - if sell_amount < buy_amount: - amount = sell_amount - else: - idx -= 1 - amount = buy_amount - if is_closed: - b_order.filled -= amount - b_order.order_update_date = datetime.now(timezone.utc) - sell_amount -= amount - profit += self.calc_profit2(avg_rate, sell_rate, amount) - realized_profit += self.calc_profit2(buy_rate, sell_rate, amount) + profit = self.calc_profit2(self.open_rate, sell_rate, sell_amount) if is_closed: - b_order2 = orders[idx] - amount2 = b_order2.safe_amount_after_fee - b_order2.average = (b_order2.average * amount2 - profit / (1 + self.fee_open)) / amount2 - if is_non_bt: - Order.query.session.commit() - self.recalc_trade_from_orders() - self.realized_profit += realized_profit + self.amount -= sell_amount + self.stake_amount = self.open_rate * self.amount + self.realized_profit += profit self.close_profit_abs = profit self.close_profit = sell_stake_amount / (sell_stake_amount - profit) - 1 + self.recalc_open_trade_value() if is_non_bt: Trade.commit() @@ -578,7 +555,7 @@ class LocalTrade(): """ self.close_rate = rate self.close_profit = self.calc_profit_ratio() - self.close_profit_abs = self.calc_profit() + self.close_profit_abs = self.calc_profit() + self.realized_profit self.close_date = self.close_date or datetime.utcnow() self.is_open = False self.sell_order_status = 'closed' @@ -699,18 +676,21 @@ class LocalTrade(): def recalc_trade_from_orders(self): total_amount = 0.0 total_stake = 0.0 + avg_price = None + for o in self.orders: - if (o.ft_is_open or - (o.ft_order_side != 'buy') or - not o.filled or - (o.status not in NON_OPEN_EXCHANGE_STATES)): + if o.ft_is_open or not o.filled: continue tmp_amount = o.safe_amount_after_fee tmp_price = o.safe_price + is_sell = o.ft_order_side != 'buy' + side = [1, -1][is_sell] if tmp_amount > 0.0 and tmp_price is not None: - total_amount += tmp_amount - total_stake += tmp_price * tmp_amount + total_amount += tmp_amount * side + total_stake += [tmp_price, avg_price][is_sell] * tmp_amount * side + if total_amount > 0: + avg_price = total_stake / total_amount if total_amount > 0: self.open_rate = total_stake / total_amount @@ -915,6 +895,7 @@ class Trade(_DECL_BASE, LocalTrade): def __init__(self, **kwargs): super().__init__(**kwargs) + self.realized_profit = 0 self.recalc_open_trade_value() def delete(self) -> None: diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 306b1ff4e..ae840ed6d 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -294,11 +294,11 @@ class Telegram(RPCHandler): if self._rpc._fiat_converter: cp_fiat = self._rpc._fiat_converter.convert_amount( msg['cumulative_profit'], msg['stake_currency'], msg['fiat_currency']) - cp_extra = f" / {cp_fiat:.3f} {msg['fiat_currency']})" + cp_extra = f" / {cp_fiat:.3f} {msg['fiat_currency']}" else: cp_extra = '' cp_extra = f"*Cumulative Profit:* (`{msg['cumulative_profit']:.8f}" \ - f"{msg['stake_currency']}{cp_extra}`)\n" + f" {msg['stake_currency']}{cp_extra}`)\n" else: cp_extra = '' message = ( diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e4a6d2e47..4a39d0e7d 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4588,7 +4588,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert trade.open_order_id is None assert pytest.approx(trade.open_rate) == 9.90909090909 assert trade.amount == 22 - assert trade.stake_amount == 218 + assert pytest.approx(trade.stake_amount) == 218 orders = Order.query.all() assert orders @@ -4672,8 +4672,8 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert trade.open_order_id is None assert trade.is_open assert trade.amount == 22 - assert trade.stake_amount == 203.59850374064837 - assert pytest.approx(trade.open_rate) == 9.254477442756745 + assert trade.stake_amount == 192.05405405405406 + assert pytest.approx(trade.open_rate) == 8.729729729729 orders = Order.query.all() assert orders @@ -4804,6 +4804,11 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: trade = Trade.query.first() assert trade assert trade.open_order_id is None + assert trade.amount == 50 + assert trade.open_rate == 11 + assert trade.stake_amount == 550 + assert pytest.approx(trade.realized_profit) == -152.375 + assert pytest.approx(trade.close_profit_abs) == -152.375 orders = Order.query.all() assert orders @@ -4843,8 +4848,11 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: trade = Trade.query.first() assert trade assert trade.open_order_id is None - assert trade.close_profit_abs == 94.25 - + assert trade.amount == 50 + assert trade.open_rate == 11 + assert trade.stake_amount == 550 + assert pytest.approx(trade.realized_profit) == -152.375 + assert pytest.approx(trade.close_profit_abs) == 94.25 orders = Order.query.all() assert orders assert len(orders) == 3 @@ -4866,12 +4874,12 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: ('sell', 50, 5), ), ( - # amount, open_rate, stake_amount, realized_profit, unrealized_profit - (100.0, 10.0, 1000.0, 0.0, -5.0), - (200.0, 12.5, 2500.0, 0.0, 486.25), - (150.0, 12.686616791354945, 1902.9925187032418, -153.375, -112.25), - (50.0, -1.7406483790523748, -87.03241895261874, 588.5, 1084.75), - (50.0, -1.7406483790523748, -87.03241895261874, 588.5, 336.625), + # amount, open_rate, stake_amount, cumulative_profit, realized_profit + (100.0, 10.0, 1000.0, 0.0, None,), + (200.0, 12.5, 2500.0, 0.0, None,), + (150.0, 12.5, 1875.0, -28.0625, -28.0625,), + (50.0, 12.5, 625.0, 713.8125, 741.875,), + (50.0, 12.5, 625.0, 713.8125, 336.625,), ) ), ( @@ -4884,17 +4892,16 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: ('sell', 150, 23), ), ( - (100.0, 3.0, 300.0, 0.0, -1.5), - (200.0, 5.0, 1000.0, 0.0, 394.0), - (100.0, -0.9451371571072332, -94.51371571072332, 395.5, 1192.0), - (250.0, 8.621945137157107, 2155.4862842892767, 395.5, 1579.75), - (150.0, 1.766417290108061, 264.96259351620915, 787.0, 2577.25), - (150.0, 1.766417290108061, 264.96259351620915, 787.0, 3175.75), + (100.0, 3.0, 300.0, 0.0, None,), + (200.0, 5.0, 1000.0, 0.0, None,), + (100.0, 5.0, 500.0, 596.0, 596.0,), + (250.0, 11.0, 2750.0, 596.0, 596.0,), + (150.0, 11.0, 1650.0, 1388.5, 792.5,), + (150.0, 11.0, 1650.0, 1388.5, 3175.75,), ) ), ]) def test_position_adjust3(mocker, default_conf_usdt, fee, orders, res) -> None: - default_conf_usdt.update({ "position_adjustment_enable": True, "dry_run": False, @@ -4965,7 +4972,7 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, orders, res) -> None: assert trade.open_rate == res[idx][1] assert trade.stake_amount == res[idx][2] assert pytest.approx(trade.realized_profit) == res[idx][3] - assert trade.calc_profit(order[2]) == res[idx][4] + assert pytest.approx(trade.close_profit_abs) == res[idx][4] order_obj = trade.select_order(order[0], False) assert order_obj.order_id == f'60{idx}' @@ -4973,7 +4980,6 @@ def test_position_adjust3(mocker, default_conf_usdt, fee, orders, res) -> None: trade = Trade.query.first() assert trade assert trade.open_order_id is None - assert trade.close_profit_abs == res[idx][4] assert trade.is_open is False diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 71a22b055..71975764e 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1484,7 +1484,8 @@ def test_recalc_trade_from_orders(fee): assert pytest.approx(trade.fee_open_cost) == o1_fee_cost + o2_fee_cost + o3_fee_cost assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val - # Just to make sure sell orders are ignored, let's calculate one more time. + # Just to make sure non partial sell orders are ignored, let's calculate one more time. + sell1 = Order( ft_order_side='sell', ft_pair=trade.pair, @@ -1641,32 +1642,6 @@ def test_recalc_trade_from_orders_ignores_bad_orders(fee): assert trade.open_trade_value == 2 * o1_trade_val assert trade.nr_of_successful_buys == 2 - # Just to make sure sell orders are ignored, let's calculate one more time. - sell1 = Order( - ft_order_side='sell', - ft_pair=trade.pair, - ft_is_open=False, - status="closed", - symbol=trade.pair, - order_type="market", - side="sell", - price=4, - average=3, - filled=2, - remaining=1, - cost=5, - order_date=trade.open_date, - order_filled_date=trade.open_date, - ) - trade.orders.append(sell1) - trade.recalc_trade_from_orders() - - assert trade.amount == 2 * o1_amount - assert trade.stake_amount == 2 * o1_amount - assert trade.open_rate == o1_rate - assert trade.fee_open_cost == 2 * o1_fee_cost - assert trade.open_trade_value == 2 * o1_trade_val - assert trade.nr_of_successful_buys == 2 # Check with 1 order order_noavg = Order( ft_order_side='buy',