diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index da2c9b4cd..51f9a03a0 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -460,6 +460,7 @@ class FreqtradeBot(LoggingMixin): else: logger.debug("Max adjustment entries is set to unlimited.") current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") + current_rate_sell = self.exchange.get_rate(trade.pair, refresh=True, side="sell") current_profit = trade.calc_profit_ratio(current_rate) min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, @@ -470,7 +471,8 @@ class FreqtradeBot(LoggingMixin): stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_rate, - current_profit=current_profit, min_stake=min_stake_amount, max_stake=max_stake_amount) + current_profit=current_profit, min_stake=min_stake_amount, + max_stake=max_stake_amount, current_rate_sell=current_rate_sell) if stake_amount is not None and stake_amount > 0.0: # We should increase our position @@ -487,7 +489,7 @@ class FreqtradeBot(LoggingMixin): } self.rpc.send_msg(msg) return - amount = -stake_amount / current_rate + amount = -stake_amount / current_rate_sell if trade.amount - amount < min_stake_amount: logger.info('Remaining amount would be too small') return diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a4eee310d..93c3676cd 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -539,14 +539,14 @@ class LocalTrade(): 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(buy_rate, sell_rate, amount) if is_closed: b_order2 = orders[idx] - amount2 = b_order2.filled or b_order2.amount - b_order2.average = (b_order2.average * amount2 - profit) / amount2 + amount2 = b_order2.safe_amount_after_fee + b_order2.average = (b_order2.average * amount2 - profit / (1 + self.fee_open)) / amount2 b_order2.order_update_date = datetime.now(timezone.utc) - self.update_order(b_order2) if is_non_bt: Order.query.session.commit() self.recalc_trade_from_orders() @@ -693,8 +693,13 @@ class LocalTrade(): def recalc_trade_from_orders(self): # mdebug - # import json + import json # logger.info(json.dumps(self.to_json(), indent =4)) + if len(self.select_filled_orders('buy')) < 2 and 0: + # Just in case, still recalc open trade value + # needs to remove + self.recalc_open_trade_value() + return total_amount = 0.0 total_stake = 0.0 for o in self.orders: @@ -719,6 +724,7 @@ class LocalTrade(): if self.stop_loss_pct is not None and self.open_rate is not None: self.adjust_stop_loss(self.open_rate, self.stop_loss_pct) # logger.info(json.dumps(self.to_json(), indent =4)) + # mdebug def select_order_by_order_id(self, order_id: str) -> Optional[Order]: """ diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 2ffa29bfd..f66e6cf6e 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -279,7 +279,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, # Simulate buy & sell oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') trade.update_trade(oobj) trade.close_date = datetime.utcnow() @@ -419,7 +420,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee # Update the ticker with a market going up mocker.patch.multiple( @@ -435,7 +437,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee # Update the ticker with a market going up mocker.patch.multiple( @@ -503,8 +506,10 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee # Update the ticker with a market going up + mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_sell_up, @@ -850,7 +855,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee # Simulate fulfilled LIMIT_SELL order for trade oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') @@ -886,7 +892,8 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee # Simulate fulfilled LIMIT_SELL order for trade oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') @@ -960,8 +967,8 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) - + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee # Simulate fulfilled LIMIT_SELL order for trade oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') trade.update_trade(oobj) @@ -1034,7 +1041,8 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, # Simulate fulfilled LIMIT_BUY order for trade oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') - trade.update_trade(oobj) + trade.open_rate = oobj.safe_price + trade.amount = oobj.safe_amount_after_fee # Simulate fulfilled LIMIT_SELL order for trade oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 76aa1bfcd..5c4ed4edc 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4620,7 +4620,6 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert trade assert trade.open_order_id is None assert trade.is_open - print(trade.is_open) assert trade.amount == 22 assert trade.stake_amount == 203.5625 assert pytest.approx(trade.open_rate) == 9.252840909090908 @@ -4635,7 +4634,11 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: - return + """ + buy 100 @ 11 + sell 50 @ 8 + sell 50 @ 16 + """ patch_RPCManager(mocker) patch_exchange(mocker) patch_wallet(mocker, free=10000) @@ -4693,8 +4696,8 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert trade assert trade.is_open is True assert trade.open_order_id is None - assert trade.open_rate == 11 - assert trade.stake_amount == 1100 + assert trade.open_rate == bid + assert trade.stake_amount == bid * amount # Assume it does nothing since order is closed and trade is open freqtrade.update_closed_trades_without_assigned_fees() @@ -4703,8 +4706,8 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert trade assert trade.is_open is True assert trade.open_order_id is None - assert trade.open_rate == 11 - assert trade.stake_amount == 1100 + assert trade.open_rate == bid + assert trade.stake_amount == bid * amount assert not trade.fee_updated('buy') freqtrade.check_handle_timedout() @@ -4713,21 +4716,23 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert trade assert trade.is_open is True assert trade.open_order_id is None - assert trade.open_rate == 11 - assert trade.stake_amount == 1100 + assert trade.open_rate == bid + assert trade.stake_amount == bid * amount assert not trade.fee_updated('buy') + amount = 50 + ask = 8 closed_sell_dca_order_1 = { 'ft_pair': pair, 'status': 'closed', 'ft_order_side': 'sell', 'side': 'sell', 'type': 'limit', - 'price': 8, - 'average': 8, - 'amount': 50, - 'filled': 50, - 'cost': 120, + 'price': ask, + 'average': ask, + 'amount': amount, + 'filled': amount, + 'cost': amount * ask, 'ft_is_open': False, 'id': '601', 'order_id': '601' @@ -4738,18 +4743,16 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: MagicMock(return_value=closed_sell_dca_order_1)) mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', MagicMock(return_value=closed_sell_dca_order_1)) - assert freqtrade.execute_trade_exit(trade=trade, limit=8, + assert freqtrade.execute_trade_exit(trade=trade, limit=ask, sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS), - sub_trade_amt=50) + sub_trade_amt=amount) trades: List[Trade] = trade.get_open_trades_without_assigned_fees() assert len(trades) == 1 # Assert trade is as expected (averaged dca) + trade = Trade.query.first() assert trade assert trade.open_order_id is None - assert trade.amount == 100 - assert trade.stake_amount == 1100 - assert pytest.approx(trade.open_rate) == 11.0 orders = Order.query.all() assert orders @@ -4758,6 +4761,47 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: order = trade.select_order('sell', False) assert order.order_id == '601' + amount = 50 + ask = 16 + closed_sell_dca_order_2 = { + 'ft_pair': pair, + 'status': 'closed', + 'ft_order_side': 'sell', + 'side': 'sell', + 'type': 'limit', + 'price': ask, + 'average': ask, + 'amount': amount, + 'filled': amount, + 'cost': amount * ask, + 'ft_is_open': False, + 'id': '602', + 'order_id': '602' + } + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=closed_sell_dca_order_2)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + MagicMock(return_value=closed_sell_dca_order_2)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + MagicMock(return_value=closed_sell_dca_order_2)) + assert freqtrade.execute_trade_exit(trade=trade, limit=ask, + sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS), + sub_trade_amt=amount) + # Assert trade is as expected (averaged dca) + + trade = Trade.query.first() + assert trade + assert trade.open_order_id is None + assert trade.close_profit_abs == 94.25 + + orders = Order.query.all() + assert orders + assert len(orders) == 3 + + # Make sure the closed order is found as the second order. + order = trade.select_order('sell', False) + assert order.order_id == '602' + def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None: default_conf_usdt.update({