diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9e9387b70..b7318279c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -292,8 +292,8 @@ class FreqtradeBot(LoggingMixin): for trade in trades: if trade.is_open and not trade.fee_updated('buy'): order = trade.select_order('buy', False) - if order: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") + open_order = trade.select_order('buy', True) + if order and open_order is None: self.update_trade_state(trade, order.order_id, send_msg=False) def handle_insufficient_funds(self, trade: Trade): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index c0634588d..31d9cd4c6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4310,10 +4310,17 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None: assert valid_price_at_min_alwd > custom_price_under_min_alwd assert valid_price_at_min_alwd < proposed_price -def test_position_adjust(mocker, default_conf_usdt, fee, limit_buy_order_usdt, - limit_buy_order_usdt_open) -> None: + +def test_position_adjust(mocker, default_conf_usdt, fee) -> None: patch_RPCManager(mocker) patch_exchange(mocker) + patch_wallet(mocker, free=10000) + default_conf_usdt.update({ + "position_adjustment_enable": True, + "dry_run": False, + "stake_amount": 10.0, + "dry_run_wallet": 1000.0, + }) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) bid = 11 @@ -4333,23 +4340,26 @@ def test_position_adjust(mocker, default_conf_usdt, fee, limit_buy_order_usdt, pair = 'ETH/USDT' # Initial buy - limit_buy_order_usdt['status'] = 'closed' - limit_buy_order_usdt['pair'] = pair - limit_buy_order_usdt['ft_pair'] = pair - limit_buy_order_usdt['ft_order_side'] = 'buy' - limit_buy_order_usdt['side'] = 'buy' - limit_buy_order_usdt['price'] = bid - limit_buy_order_usdt['average'] = bid - limit_buy_order_usdt['cost'] = bid * stake_amount - limit_buy_order_usdt['amount'] = stake_amount - limit_buy_order_usdt['filled'] = stake_amount - limit_buy_order_usdt['ft_is_open'] = False - limit_buy_order_usdt['id'] = '650' - limit_buy_order_usdt['order_id'] = '650' + closed_successful_buy_order = { + 'pair': pair, + 'ft_pair': pair, + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'status': 'closed', + 'price': bid, + 'average': bid, + 'cost': bid * stake_amount, + 'amount': stake_amount, + 'filled': stake_amount, + 'ft_is_open': False, + 'id': '650', + 'order_id': '650' + } mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=limit_buy_order_usdt)) + MagicMock(return_value=closed_successful_buy_order)) mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - MagicMock(return_value=limit_buy_order_usdt)) + MagicMock(return_value=closed_successful_buy_order)) assert freqtrade.execute_entry(pair, stake_amount) # Should create an closed trade with an no open order id # Order is filled and trade is open @@ -4363,7 +4373,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee, limit_buy_order_usdt, assert trade.open_rate == 11 assert trade.stake_amount == 110 - # Assume it does nothing since trade is still open and no new orders + # Assume it does nothing since order is closed and trade is open freqtrade.update_closed_trades_without_assigned_fees() trade = Trade.query.first() @@ -4372,22 +4382,36 @@ def test_position_adjust(mocker, default_conf_usdt, fee, limit_buy_order_usdt, assert trade.open_order_id is None assert trade.open_rate == 11 assert trade.stake_amount == 110 + assert not trade.fee_updated('buy') + + freqtrade.check_handle_timedout() + + trade = Trade.query.first() + assert trade + assert trade.is_open is True + assert trade.open_order_id is None + assert trade.open_rate == 11 + assert trade.stake_amount == 110 + assert not trade.fee_updated('buy') # First position adjustment buy. - limit_buy_order_usdt_open['ft_pair'] = pair - limit_buy_order_usdt_open['ft_order_side'] = 'buy' - limit_buy_order_usdt_open['side'] = 'buy' - limit_buy_order_usdt_open['status'] = None - limit_buy_order_usdt_open['price'] = 9 - limit_buy_order_usdt_open['amount'] = 12 - limit_buy_order_usdt_open['cost'] = 100 - limit_buy_order_usdt_open['ft_is_open'] = True - limit_buy_order_usdt_open['id'] = 651 - limit_buy_order_usdt_open['order_id'] = 651 + open_dca_order_1 = { + 'ft_pair': pair, + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'status': None, + 'price': 9, + 'amount': 12, + 'cost': 100, + 'ft_is_open': True, + 'id': '651', + 'order_id': '651' + } mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=limit_buy_order_usdt_open)) + MagicMock(return_value=open_dca_order_1)) mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - MagicMock(return_value=limit_buy_order_usdt_open)) + MagicMock(return_value=open_dca_order_1)) assert freqtrade.execute_entry(pair, stake_amount, trade=trade) orders = Order.query.all() @@ -4399,10 +4423,28 @@ def test_position_adjust(mocker, default_conf_usdt, fee, limit_buy_order_usdt, assert trade.open_rate == 11 assert trade.amount == 10 assert trade.stake_amount == 110 + assert not trade.fee_updated('buy') + trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + assert len(trades) == 1 + assert trade.is_open + assert not trade.fee_updated('buy') + order = trade.select_order('buy', False) + assert order + assert order.order_id == '650' - # Assume it does nothing since trade is still open - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - MagicMock(return_value=limit_buy_order_usdt_open)) + def make_sure_its_651(*apos, **kwargs): + + if apos[0] == '650': + return closed_successful_buy_order + if apos[0] == '651': + return open_dca_order_1 + return None + + # Assume it does nothing since order is still open + fetch_order_mm = MagicMock(side_effect=make_sure_its_651) + mocker.patch('freqtrade.exchange.Exchange.create_order', fetch_order_mm) + mocker.patch('freqtrade.exchange.Exchange.fetch_order', fetch_order_mm) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', fetch_order_mm) freqtrade.update_closed_trades_without_assigned_fees() orders = Order.query.all() @@ -4418,27 +4460,35 @@ def test_position_adjust(mocker, default_conf_usdt, fee, limit_buy_order_usdt, assert trade.open_rate == 11 assert trade.amount == 10 assert trade.stake_amount == 110 - assert trade.fee_updated('buy') is False + assert not trade.fee_updated('buy') # Make sure the closed order is found as the first order. order = trade.select_order('buy', False) assert order.order_id == '650' # Now close the order so it should update. - limit_buy_order_usdt_open['ft_pair'] = pair - limit_buy_order_usdt_open['status'] = 'closed' - limit_buy_order_usdt_open['ft_order_side'] = 'buy' - limit_buy_order_usdt_open['side'] = 'buy' - limit_buy_order_usdt_open['price'] = 9 - limit_buy_order_usdt_open['average'] = 9 - limit_buy_order_usdt_open['amount'] = 12 - limit_buy_order_usdt_open['filled'] = 12 - limit_buy_order_usdt_open['cost'] = 108 - limit_buy_order_usdt_open['ft_is_open'] = False - limit_buy_order_usdt_open['id'] = '651' - limit_buy_order_usdt_open['order_id'] = '651' + closed_dca_order_1 = { + 'ft_pair': pair, + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'status': 'closed', + 'price': 9, + 'average': 9, + 'amount': 12, + 'filled': 12, + 'cost': 108, + 'ft_is_open': False, + 'id': '651', + 'order_id': '651' + } + + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=closed_dca_order_1)) mocker.patch('freqtrade.exchange.Exchange.fetch_order', - MagicMock(return_value=limit_buy_order_usdt_open)) + MagicMock(return_value=closed_dca_order_1)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + MagicMock(return_value=closed_dca_order_1)) freqtrade.check_handle_timedout() # Assert trade is as expected (averaged dca) @@ -4461,3 +4511,42 @@ def test_position_adjust(mocker, default_conf_usdt, fee, limit_buy_order_usdt, trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() assert len(trades) == 1 + # Add a second DCA + closed_dca_order_2 = { + 'ft_pair': pair, + 'status': 'closed', + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'price': 7, + 'average': 7, + 'amount': 15, + 'filled': 15, + 'cost': 105, + 'ft_is_open': False, + 'id': '652', + 'order_id': '652' + } + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=closed_dca_order_2)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + MagicMock(return_value=closed_dca_order_2)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + MagicMock(return_value=closed_dca_order_2)) + assert freqtrade.execute_entry(pair, stake_amount, trade=trade) + + # Assert trade is as expected (averaged dca) + trade = Trade.query.first() + assert trade + assert trade.open_order_id is None + assert pytest.approx(trade.open_rate) == 8.729729729729 + assert trade.amount == 37 + assert trade.stake_amount == 323 + + 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('buy', False) + assert order.order_id == '652'