Improve handling of order cancelation failures with force_exit
closes #8708
This commit is contained in:
+10
-2
@@ -755,7 +755,7 @@ class RPC:
|
|||||||
return {'status': 'Reloaded from orders from exchange'}
|
return {'status': 'Reloaded from orders from exchange'}
|
||||||
|
|
||||||
def __exec_force_exit(self, trade: Trade, ordertype: Optional[str],
|
def __exec_force_exit(self, trade: Trade, ordertype: Optional[str],
|
||||||
amount: Optional[float] = None) -> None:
|
amount: Optional[float] = None) -> bool:
|
||||||
# Check if there is there is an open order
|
# Check if there is there is an open order
|
||||||
fully_canceled = False
|
fully_canceled = False
|
||||||
if trade.open_order_id:
|
if trade.open_order_id:
|
||||||
@@ -770,6 +770,9 @@ class RPC:
|
|||||||
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_EXIT'])
|
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_EXIT'])
|
||||||
|
|
||||||
if not fully_canceled:
|
if not fully_canceled:
|
||||||
|
if trade.open_order_id is not None:
|
||||||
|
# Order cancellation failed, so we can't exit.
|
||||||
|
return False
|
||||||
# Get current rate and execute sell
|
# Get current rate and execute sell
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
trade.pair, side='exit', is_short=trade.is_short, refresh=True)
|
trade.pair, side='exit', is_short=trade.is_short, refresh=True)
|
||||||
@@ -790,6 +793,9 @@ class RPC:
|
|||||||
trade, current_rate, exit_check, ordertype=order_type,
|
trade, current_rate, exit_check, ordertype=order_type,
|
||||||
sub_trade_amt=sub_amount)
|
sub_trade_amt=sub_amount)
|
||||||
|
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None, *,
|
def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None, *,
|
||||||
amount: Optional[float] = None) -> Dict[str, str]:
|
amount: Optional[float] = None) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
@@ -817,9 +823,11 @@ class RPC:
|
|||||||
logger.warning('force_exit: Invalid argument received')
|
logger.warning('force_exit: Invalid argument received')
|
||||||
raise RPCException('invalid argument')
|
raise RPCException('invalid argument')
|
||||||
|
|
||||||
self.__exec_force_exit(trade, ordertype, amount)
|
result = self.__exec_force_exit(trade, ordertype, amount)
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
self._freqtrade.wallets.update()
|
self._freqtrade.wallets.update()
|
||||||
|
if not result:
|
||||||
|
raise RPCException('Failed to exit trade.')
|
||||||
return {'result': f'Created exit order for trade {trade_id}.'}
|
return {'result': f'Created exit order for trade {trade_id}.'}
|
||||||
|
|
||||||
def _force_entry_validations(self, pair: str, order_side: SignalDirection):
|
def _force_entry_validations(self, pair: str, order_side: SignalDirection):
|
||||||
|
|||||||
+29
-4
@@ -765,7 +765,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
|||||||
cancel_order_mock.reset_mock()
|
cancel_order_mock.reset_mock()
|
||||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first()
|
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first()
|
||||||
amount = trade.amount
|
amount = trade.amount
|
||||||
# make an limit-sell open trade
|
# make an limit-sell open order trade
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
f'{EXMS}.fetch_order',
|
f'{EXMS}.fetch_order',
|
||||||
return_value={
|
return_value={
|
||||||
@@ -778,12 +778,24 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'id': trade.orders[0].order_id,
|
'id': trade.orders[0].order_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
cancel_order_3 = mocker.patch(
|
||||||
|
f'{EXMS}.cancel_order_with_result',
|
||||||
|
return_value={
|
||||||
|
'status': 'canceled',
|
||||||
|
'type': 'limit',
|
||||||
|
'side': 'sell',
|
||||||
|
'amount': amount,
|
||||||
|
'remaining': amount,
|
||||||
|
'filled': 0.0,
|
||||||
|
'id': trade.orders[0].order_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
msg = rpc._rpc_force_exit('3')
|
msg = rpc._rpc_force_exit('3')
|
||||||
assert msg == {'result': 'Created exit order for trade 3.'}
|
assert msg == {'result': 'Created exit order for trade 3.'}
|
||||||
# status quo, no exchange calls
|
# status quo, no exchange calls
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_3.call_count == 1
|
||||||
|
assert cancel_order_mock.call_count == 0
|
||||||
|
|
||||||
cancel_order_mock.reset_mock()
|
|
||||||
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first()
|
trade = Trade.session.scalars(select(Trade).filter(Trade.id == '2')).first()
|
||||||
amount = trade.amount
|
amount = trade.amount
|
||||||
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
||||||
@@ -796,10 +808,23 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'filled': None
|
'filled': None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
cancel_order_4 = mocker.patch(
|
||||||
|
f'{EXMS}.cancel_order_with_result',
|
||||||
|
return_value={
|
||||||
|
'status': 'canceled',
|
||||||
|
'type': 'limit',
|
||||||
|
'side': 'sell',
|
||||||
|
'amount': amount,
|
||||||
|
'remaining': 0.0,
|
||||||
|
'filled': amount,
|
||||||
|
'id': trade.orders[0].order_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
|
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
|
||||||
msg = rpc._rpc_force_exit('4')
|
msg = rpc._rpc_force_exit('4')
|
||||||
assert msg == {'result': 'Created exit order for trade 4.'}
|
assert msg == {'result': 'Created exit order for trade 4.'}
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_4.call_count == 1
|
||||||
|
assert cancel_order_mock.call_count == 0
|
||||||
assert trade.amount == amount
|
assert trade.amount == amount
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user