Merge pull request #7506 from freqtrade/cancel_partial_sell

Support cancellation partially filled exit orders
This commit is contained in:
Matthias
2022-10-03 19:36:51 +02:00
committed by GitHub
3 changed files with 77 additions and 32 deletions

View File

@@ -1292,7 +1292,14 @@ class Exchange:
order = self.fetch_order(order_id, pair)
except InvalidOrderException:
logger.warning(f"Could not fetch cancelled order {order_id}.")
order = {'id': order_id, 'fee': {}, 'status': 'canceled', 'amount': amount, 'info': {}}
order = {
'id': order_id,
'status': 'canceled',
'amount': amount,
'filled': 0.0,
'fee': {},
'info': {}
}
return order

View File

@@ -1409,47 +1409,63 @@ class FreqtradeBot(LoggingMixin):
:return: True if exit order was cancelled, false otherwise
"""
cancelled = False
# if trade is not partially completed, just cancel the order
if order['remaining'] == order['amount'] or order.get('filled') == 0.0:
if not self.exchange.check_order_canceled_empty(order):
try:
# if trade is not partially completed, just delete the order
co = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
trade.amount)
trade.update_order(co)
except InvalidOrderException:
logger.exception(
f"Could not cancel {trade.exit_side} order {trade.open_order_id}")
return False
logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade)
else:
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade)
trade.update_order(order)
# Cancelled orders may have the status of 'canceled' or 'closed'
if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
filled_val: float = order.get('filled', 0.0) or 0.0
filled_rem_stake = trade.stake_amount - filled_val * trade.open_rate
minstake = self.exchange.get_min_pair_stake_amount(
trade.pair, trade.open_rate, self.strategy.stoploss)
# Double-check remaining amount
if filled_val > 0:
reason = constants.CANCEL_REASON['PARTIALLY_FILLED']
if minstake and filled_rem_stake < minstake:
logger.warning(
f"Order {trade.open_order_id} for {trade.pair} not cancelled, as "
f"the filled amount of {filled_val} would result in an unexitable trade.")
reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
self._notify_exit_cancel(
trade,
order_type=self.strategy.order_types['exit'],
reason=reason, order_id=order['id'],
sub_trade=trade.amount != order['amount']
)
return False
try:
co = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
trade.amount)
except InvalidOrderException:
logger.exception(
f"Could not cancel {trade.exit_side} order {trade.open_order_id}")
return False
trade.close_rate = None
trade.close_rate_requested = None
trade.close_profit = None
trade.close_profit_abs = None
trade.open_order_id = None
trade.exit_reason = None
# Set exit_reason for fill message
exit_reason_prev = trade.exit_reason
trade.exit_reason = trade.exit_reason + f", {reason}" if trade.exit_reason else reason
self.update_trade_state(trade, trade.open_order_id, co)
# Order might be filled above in odd timing issues.
if co.get('status') in ('canceled', 'cancelled'):
trade.exit_reason = None
else:
trade.exit_reason = exit_reason_prev
logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.')
cancelled = True
self.wallets.update()
else:
# TODO: figure out how to handle partially complete sell orders
reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
cancelled = False
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.')
self.update_trade_state(trade, trade.open_order_id, order)
order_obj = trade.select_order_by_order_id(order['id'])
if not order_obj:
raise DependencyException(
f"Order_obj not found for {order['id']}. This should not have happened.")
self.wallets.update()
sub_trade = order_obj.amount != trade.amount
self._notify_exit_cancel(
trade,
order_type=self.strategy.order_types['exit'],
reason=reason, order=order_obj, sub_trade=sub_trade
reason=reason, order_id=order['id'], sub_trade=trade.amount != order['amount']
)
return cancelled
@@ -1646,7 +1662,7 @@ class FreqtradeBot(LoggingMixin):
self.rpc.send_msg(msg)
def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str,
order: Order, sub_trade: bool = False) -> None:
order_id: str, sub_trade: bool = False) -> None:
"""
Sends rpc notification when a sell cancel occurred.
"""
@@ -1655,6 +1671,11 @@ class FreqtradeBot(LoggingMixin):
else:
trade.exit_order_status = reason
order = trade.select_order_by_order_id(order_id)
if not order:
raise DependencyException(
f"Order_obj not found for {order_id}. This should not have happened.")
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit_trade = trade.calc_profit(rate=profit_rate)
current_rate = self.exchange.get_rate(