Fix cancel order deleting trade

if one order was successfully filled, the trade cannot be deleted.

closes #6907
This commit is contained in:
Matthias 2022-05-31 17:49:51 +02:00
parent eee337c764
commit 88845f6d88
2 changed files with 14 additions and 7 deletions

View File

@ -1203,15 +1203,15 @@ class FreqtradeBot(LoggingMixin):
current_order_rate=order_obj.price, entry_tag=trade.enter_tag, current_order_rate=order_obj.price, entry_tag=trade.enter_tag,
side=trade.entry_side) side=trade.entry_side)
full_cancel = False replacing = True
cancel_reason = constants.CANCEL_REASON['REPLACE'] cancel_reason = constants.CANCEL_REASON['REPLACE']
if not adjusted_entry_price: if not adjusted_entry_price:
full_cancel = True if trade.nr_of_successful_entries == 0 else False replacing = False
cancel_reason = constants.CANCEL_REASON['USER_CANCEL'] cancel_reason = constants.CANCEL_REASON['USER_CANCEL']
if order_obj.price != adjusted_entry_price: if order_obj.price != adjusted_entry_price:
# cancel existing order if new price is supplied or None # cancel existing order if new price is supplied or None
self.handle_cancel_enter(trade, order, cancel_reason, self.handle_cancel_enter(trade, order, cancel_reason,
allow_full_cancel=full_cancel) replacing=replacing)
if adjusted_entry_price: if adjusted_entry_price:
# place new order only if new price is supplied # place new order only if new price is supplied
self.execute_entry( self.execute_entry(
@ -1245,10 +1245,11 @@ class FreqtradeBot(LoggingMixin):
def handle_cancel_enter( def handle_cancel_enter(
self, trade: Trade, order: Dict, reason: str, self, trade: Trade, order: Dict, reason: str,
allow_full_cancel: Optional[bool] = True replacing: Optional[bool] = False
) -> bool: ) -> bool:
""" """
Buy cancel - cancel order Buy cancel - cancel order
:param replacing: Replacing order - prevent trade deletion.
:return: True if order was fully cancelled :return: True if order was fully cancelled
""" """
was_trade_fully_canceled = False was_trade_fully_canceled = False
@ -1286,7 +1287,7 @@ class FreqtradeBot(LoggingMixin):
if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC):
# if trade is not partially completed and it's the only order, just delete the trade # if trade is not partially completed and it's the only order, just delete the trade
open_order_count = len([order for order in trade.orders if order.status == 'open']) open_order_count = len([order for order in trade.orders if order.status == 'open'])
if open_order_count <= 1 and allow_full_cancel: if open_order_count <= 1 and trade.nr_of_successful_entries == 0 and not replacing:
logger.info(f'{side} order fully cancelled. Removing {trade} from database.') logger.info(f'{side} order fully cancelled. Removing {trade} from database.')
trade.delete() trade.delete()
was_trade_fully_canceled = True was_trade_fully_canceled = True
@ -1295,7 +1296,7 @@ class FreqtradeBot(LoggingMixin):
# FIXME TODO: This could possibly reworked to not duplicate the code 15 lines below. # FIXME TODO: This could possibly reworked to not duplicate the code 15 lines below.
self.update_trade_state(trade, trade.open_order_id, corder) self.update_trade_state(trade, trade.open_order_id, corder)
trade.open_order_id = None trade.open_order_id = None
logger.info(f'Partial {side} order timeout for {trade}.') logger.info(f'{side} Order timeout for {trade}.')
else: else:
# if trade is partially complete, edit the stake details for the trade # if trade is partially complete, edit the stake details for the trade
# and close the order # and close the order

View File

@ -2572,6 +2572,7 @@ def test_check_handle_cancelled_buy(
get_fee=fee get_fee=fee
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.orders = []
open_trade.is_short = is_short open_trade.is_short = is_short
Trade.query.session.add(open_trade) Trade.query.session.add(open_trade)
@ -2954,6 +2955,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
# TODO: Convert to real trade
trade = MagicMock() trade = MagicMock()
trade.pair = 'LTC/USDT' trade.pair = 'LTC/USDT'
trade.open_rate = 200 trade.open_rate = 200
@ -2961,6 +2963,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
trade.entry_side = "buy" trade.entry_side = "buy"
l_order['filled'] = 0.0 l_order['filled'] = 0.0
l_order['status'] = 'open' l_order['status'] = 'open'
trade.nr_of_successful_entries = 0
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_enter(trade, l_order, reason) assert freqtrade.handle_cancel_enter(trade, l_order, reason)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
@ -3003,7 +3006,9 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
# TODO: Convert to real trade
trade = MagicMock() trade = MagicMock()
trade.nr_of_successful_entries = 0
trade.pair = 'LTC/ETH' trade.pair = 'LTC/ETH'
trade.entry_side = "sell" if is_short else "buy" trade.entry_side = "sell" if is_short else "buy"
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason) assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
@ -3036,13 +3041,14 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
# TODO: Convert to real trade
trade = MagicMock() trade = MagicMock()
trade.pair = 'LTC/USDT' trade.pair = 'LTC/USDT'
trade.entry_side = "buy" trade.entry_side = "buy"
trade.open_rate = 200 trade.open_rate = 200
trade.entry_side = "buy" trade.entry_side = "buy"
trade.open_order_id = "open_order_noop" trade.open_order_id = "open_order_noop"
trade.nr_of_successful_entries = 0
l_order['filled'] = 0.0 l_order['filled'] = 0.0
l_order['status'] = 'open' l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']