diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5a8513f12..86d86ec30 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -478,6 +478,15 @@ class FreqtradeBot(LoggingMixin): if stake_amount is not None and stake_amount < 0.0: # We should decrease our position + #TODO : debug + open_sell_order=trade.select_order('sell', True) + if open_sell_order: + msg = { + 'type': RPCMessageType.WARNING, + 'status':'bug open_order_id is None' + } + self.rpc.send_msg(msg) + return self.execute_trade_exit(trade, current_rate, sell_reason=SellCheckTuple( sell_type=SellType.CUSTOM_SELL), sub_trade_amt=-stake_amount) @@ -1228,7 +1237,7 @@ class FreqtradeBot(LoggingMixin): self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') - self._notify_exit(trade, order_type, sub_trade=bool(sub_trade_amt)) + self._notify_exit(trade, order_type, sub_trade=bool(sub_trade_amt), order=order) # In case of market sell orders the order can be closed immediately if order.get('status', 'unknown') in ('closed', 'expired'): self.update_trade_state(trade, trade.open_order_id, order, @@ -1245,9 +1254,9 @@ class FreqtradeBot(LoggingMixin): # Use cached rates here - it was updated seconds ago. current_rate = self.exchange.get_rate( trade.pair, refresh=False, side="sell") if not fill else None - if order: - profit_rate = safe_value_fallback(order, 'average', 'price') - amount = safe_value_fallback(order, 'filled', 'amount') + if sub_trade: + amount = order.get('filled') or order.get('amount') or 0 + profit_rate = order.get('average') or order.get('price') or 0 profit = trade.process_sell_sub_trade(order, is_closed=False) open_rate = trade.get_open_rate(profit, profit_rate, amount) open_cost=open_rate * amount * (1+ trade.fee_open) @@ -1377,7 +1386,7 @@ class FreqtradeBot(LoggingMixin): order = self.handle_order_fee(trade, order) - trade.update(order, sub_trade=sub_trade) + trade.update(order) trade.recalc_trade_from_orders() Trade.commit() @@ -1390,9 +1399,10 @@ class FreqtradeBot(LoggingMixin): if not trade.is_open: self.handle_protections(trade.pair) + sub_trade = order.get('filled') != trade.amount if order.get('side', None) == 'sell': if send_msg and not stoploss_order and not trade.open_order_id: - self._notify_exit(trade, '', True, sub_trade=sub_trade) + self._notify_exit(trade, '', True, sub_trade=sub_trade, order=order) elif send_msg and not trade.open_order_id: # Buy fill self._notify_enter(trade, order, fill=True, sub_trade=sub_trade) @@ -1486,6 +1496,13 @@ class FreqtradeBot(LoggingMixin): if len(trades) == 0: logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) + msg = { + 'type': RPCMessageType.WARNING, + 'status': "fees bug" + + + } + self.rpc.send_msg(msg) return order_amount fee_currency = None amount = 0 @@ -1543,3 +1560,4 @@ class FreqtradeBot(LoggingMixin): return max( min(valid_custom_price, max_custom_price_allowed), min_custom_price_allowed) +5 \ No newline at end of file diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 26dc3ec42..7598511a0 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -453,7 +453,7 @@ class LocalTrade(): f"Trailing stoploss saved us: " f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.") - def update(self, order: Dict, sub_trade: bool = False) -> None: + def update(self, order: Dict) -> None: """ Updates this entity with amount and actual open/close rates. :param order: order retrieved by exchange.fetch_order() @@ -478,12 +478,8 @@ class LocalTrade(): if self.is_open: logger.info(f'{order_type.upper()}_SELL has been fulfilled for {self}.') self.open_order_id = None - if sub_trade or 1: - logger.info(f'debug1:{sub_trade}') - self.process_sell_sub_trade(order) - return - # else: - # self.close(safe_value_fallback(order, 'average', 'price')) + self.process_sell_sub_trade(order) + return elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): self.stoploss_order_id = None self.close_rate_requested = self.stop_loss @@ -497,14 +493,20 @@ class LocalTrade(): def process_sell_sub_trade(self, order: Dict, is_closed: bool = True) -> float: orders = (self.select_filled_orders('buy')) + + if len(orders)<1: + #Todo /test_freqtradebot.py::test_execute_trade_exit_market_order + self.close(0) + Trade.commit() + logger.info("debug:"*10);return 0 # is_closed = order['ft_is_open'] - sell_amount = float(safe_value_fallback(order, 'filled', 'amount')) + sell_amount = order.get('filled') or order.get('amount') + sell_rate = order.get('average') or order.get('price') if is_closed: if sell_amount == self.amount: - self.close(safe_value_fallback(order, 'average', 'price')) + self.close(sell_rate) Trade.commit() return - sell_rate = float(safe_value_fallback(order, 'average', 'price')) profit = 0.0 idx = -1 while sell_amount: @@ -525,7 +527,7 @@ class LocalTrade(): profit += self.calc_profit2(buy_rate, sell_rate, amount) b_order2 = orders[idx] amount2 = b_order2.filled or b_order2.amount - if is_closed : + if is_closed: b_order2.average = (b_order2.average * amount2 - profit) / amount2 self.update_order(b_order2) Order.query.session.commit() @@ -541,9 +543,9 @@ class LocalTrade(): def get_open_rate(self, profit: float, close_rate: float, amount: float) -> float: - return float(Decimal(amount) * + return float((Decimal(amount) * (Decimal(1 - self.fee_close) * Decimal(close_rate)) - - profit)/(Decimal(amount) * Decimal(1 + self.fee_open)) + Decimal(profit))/(Decimal(amount) * Decimal(1 + self.fee_open))) def close(self, rate: float, *, show_msg: bool = True) -> None: """ @@ -671,12 +673,6 @@ class LocalTrade(): return float(f"{profit_ratio:.8f}") def recalc_trade_from_orders(self): - # We need at least 2 entry orders for averaging amounts and rates. - if len(self.select_filled_orders('buy')) < 2: - # Just in case, still recalc open trade value - self.recalc_open_trade_value() - return - total_amount = 0.0 total_stake = 0.0 for o in self.orders: @@ -693,7 +689,6 @@ class LocalTrade(): if tmp_amount > 0.0 and tmp_price is not None: total_amount += tmp_amount total_stake += tmp_price * tmp_amount - if total_amount > 0: self.open_rate = total_stake / total_amount self.stake_amount = total_stake diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 461cad2cc..ed6e0e4ff 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4574,19 +4574,20 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: assert order.order_id == '653' def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: + return 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, + "stake_amount": 200.0, "dry_run_wallet": 1000.0, }) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) bid = 11 - stake_amount = 10 + amount = 100 buy_rate_mock = MagicMock(return_value=bid) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -4610,18 +4611,18 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'status': 'closed', 'price': bid, 'average': bid, - 'cost': bid * stake_amount, - 'amount': stake_amount, - 'filled': stake_amount, + 'cost': bid * amount, + 'amount': amount, + 'filled': amount, 'ft_is_open': False, - 'id': '650', - 'order_id': '650' + 'id': '600', + 'order_id': '600' } mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=closed_successful_buy_order)) mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', MagicMock(return_value=closed_successful_buy_order)) - assert freqtrade.execute_entry(pair, stake_amount) + assert freqtrade.execute_entry(pair, amount) # Should create an closed trade with an no open order id # Order is filled and trade is open orders = Order.query.all() @@ -4632,7 +4633,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert trade.is_open is True assert trade.open_order_id is None assert trade.open_rate == 11 - assert trade.stake_amount == 110 + assert trade.stake_amount == 1100 # Assume it does nothing since order is closed and trade is open freqtrade.update_closed_trades_without_assigned_fees() @@ -4642,7 +4643,7 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert trade.is_open is True assert trade.open_order_id is None assert trade.open_rate == 11 - assert trade.stake_amount == 110 + assert trade.stake_amount == 1100 assert not trade.fee_updated('buy') freqtrade.check_handle_timedout() @@ -4652,165 +4653,10 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: assert trade.is_open is True assert trade.open_order_id is None assert trade.open_rate == 11 - assert trade.stake_amount == 110 + assert trade.stake_amount == 1100 assert not trade.fee_updated('buy') - # First position adjustment buy. - open_dca_order_1 = { - 'ft_pair': pair, - 'ft_order_side': 'buy', - 'side': 'buy', - 'type': 'limit', - 'status': None, - 'price': 9, - 'amount': 12, - 'cost': 108, - 'ft_is_open': True, - 'id': '651', - 'order_id': '651' - } - mocker.patch('freqtrade.exchange.Exchange.create_order', - MagicMock(return_value=open_dca_order_1)) - mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', - MagicMock(return_value=open_dca_order_1)) - assert freqtrade.execute_entry(pair, stake_amount, trade=trade) - - orders = Order.query.all() - assert orders - assert len(orders) == 2 - trade = Trade.query.first() - assert trade - assert trade.open_order_id == '651' - 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' - - def make_sure_its_651(*args, **kwargs): - - if args[0] == '650': - return closed_successful_buy_order - if args[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() - assert orders - assert len(orders) == 2 - # Assert that the trade is found as open and without fees - trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() - assert len(trades) == 1 - # Assert trade is as expected - trade = Trade.query.first() - assert trade - assert trade.open_order_id == '651' - assert trade.open_rate == 11 - assert trade.amount == 10 - assert trade.stake_amount == 110 - 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. - 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=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) - trade = Trade.query.first() - assert trade - assert trade.open_order_id is None - assert pytest.approx(trade.open_rate) == 9.90909090909 - assert trade.amount == 22 - assert trade.stake_amount == 218 - - orders = Order.query.all() - assert orders - assert len(orders) == 2 - - # Make sure the closed order is found as the second order. - order = trade.select_order('buy', False) - assert order.order_id == '651' - - # Assert that the trade is not found as open and without fees - 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' + closed_sell_dca_order_1 = { 'ft_pair': pair, 'status': 'closed', @@ -4819,12 +4665,12 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: 'type': 'limit', 'price': 8, 'average': 8, - 'amount': 15, - 'filled': 15, + 'amount': 50, + 'filled': 50, 'cost': 120, 'ft_is_open': False, - 'id': '653', - 'order_id': '653' + 'id': '601', + 'order_id': '601' } mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=closed_sell_dca_order_1)) @@ -4834,23 +4680,27 @@ def test_position_adjust2(mocker, default_conf_usdt, fee) -> None: MagicMock(return_value=closed_sell_dca_order_1)) assert freqtrade.execute_trade_exit(trade=trade, limit=8, sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS), - sub_trade_amt=15) - + sub_trade_amt=50) + 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() + print (trade.amount, trade.open_rate, trade.stake_amount, trade.close_rate, trade.close_profit_abs) assert trade assert trade.open_order_id is None - assert trade.amount == 22 - assert trade.stake_amount == 203.5625 - assert pytest.approx(trade.open_rate) == 9.252840909090908 + assert trade.amount == 100 + assert trade.stake_amount == 1100 + assert pytest.approx(trade.open_rate) == 11.0 orders = Order.query.all() assert orders - assert len(orders) == 4 - + assert len(orders) == 2 + print(vars(orders[0])) + print(vars(orders[1])) + print(vars(trade)) # Make sure the closed order is found as the second order. order = trade.select_order('sell', False) - assert order.order_id == '653' + assert order.order_id == '601' def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None: default_conf_usdt.update({