mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 18:23:55 +00:00
Merge pull request #3210 from jpribyl/Cancel_open_orders_on_shutdown
Cancel open orders during shutdown
This commit is contained in:
commit
15ef6df950
|
@ -6,6 +6,7 @@
|
|||
"fiat_display_currency": "USD",
|
||||
"ticker_interval": "5m",
|
||||
"dry_run": false,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trailing_stop": false,
|
||||
"unfilledtimeout": {
|
||||
"buy": 10,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"fiat_display_currency": "USD",
|
||||
"ticker_interval": "5m",
|
||||
"dry_run": true,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trailing_stop": false,
|
||||
"unfilledtimeout": {
|
||||
"buy": 10,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"amend_last_stake_amount": false,
|
||||
"last_stake_amount_min_ratio": 0.5,
|
||||
"dry_run": false,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"ticker_interval": "5m",
|
||||
"trailing_stop": false,
|
||||
"trailing_stop_positive": 0.005,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"fiat_display_currency": "EUR",
|
||||
"ticker_interval": "5m",
|
||||
"dry_run": true,
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"trailing_stop": false,
|
||||
"unfilledtimeout": {
|
||||
"buy": 10,
|
||||
|
|
|
@ -51,6 +51,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
|
||||
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
|
||||
| `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||
| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float (as ratio)
|
||||
|
|
|
@ -88,6 +88,7 @@ CONF_SCHEMA = {
|
|||
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
||||
'dry_run': {'type': 'boolean'},
|
||||
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
|
||||
'cancel_open_orders_on_exit': {'type': 'boolean', 'default': False},
|
||||
'process_only_new_candles': {'type': 'boolean'},
|
||||
'minimal_roi': {
|
||||
'type': 'object',
|
||||
|
@ -321,3 +322,10 @@ SCHEMA_MINIMAL_REQUIRED = [
|
|||
'dataformat_ohlcv',
|
||||
'dataformat_trades',
|
||||
]
|
||||
|
||||
CANCEL_REASON = {
|
||||
"TIMEOUT": "cancelled due to timeout",
|
||||
"PARTIALLY_FILLED": "partially filled - keeping order open",
|
||||
"ALL_CANCELLED": "cancelled (all unfilled and partially filled open orders cancelled)",
|
||||
"CANCELLED_ON_EXCHANGE": "cancelled on exchange",
|
||||
}
|
||||
|
|
|
@ -116,6 +116,9 @@ class FreqtradeBot:
|
|||
"""
|
||||
logger.info('Cleaning up modules ...')
|
||||
|
||||
if self.config['cancel_open_orders_on_exit']:
|
||||
self.cancel_all_open_orders()
|
||||
|
||||
self.rpc.cleanup()
|
||||
persistence.cleanup()
|
||||
|
||||
|
@ -165,6 +168,13 @@ class FreqtradeBot:
|
|||
|
||||
Trade.session.flush()
|
||||
|
||||
def process_stopped(self) -> None:
|
||||
"""
|
||||
Close all orders that were left open
|
||||
"""
|
||||
if self.config['cancel_open_orders_on_exit']:
|
||||
self.cancel_all_open_orders()
|
||||
|
||||
def _refresh_whitelist(self, trades: List[Trade] = []) -> List[str]:
|
||||
"""
|
||||
Refresh whitelist from pairlist or edge and extend it with trades.
|
||||
|
@ -878,11 +888,7 @@ class FreqtradeBot:
|
|||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
|
||||
self.handle_timedout_limit_buy(trade, order)
|
||||
self.wallets.update()
|
||||
order_type = self.strategy.order_types['buy']
|
||||
self._notify_buy_cancel(trade, order_type)
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
elif (order['side'] == 'sell' and (
|
||||
trade_state_update
|
||||
|
@ -891,25 +897,43 @@ class FreqtradeBot:
|
|||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
reason = self.handle_timedout_limit_sell(trade, order)
|
||||
self.wallets.update()
|
||||
order_type = self.strategy.order_types['sell']
|
||||
self._notify_sell_cancel(trade, order_type, reason)
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
|
||||
def cancel_all_open_orders(self) -> None:
|
||||
"""
|
||||
Buy timeout - cancel order
|
||||
Cancel all orders that are currently open
|
||||
:return: None
|
||||
"""
|
||||
|
||||
for trade in Trade.get_open_order_trades():
|
||||
try:
|
||||
order = self.exchange.get_order(trade.open_order_id, trade.pair)
|
||||
except (DependencyException, InvalidOrderException):
|
||||
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
||||
continue
|
||||
|
||||
if order['side'] == 'buy':
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
|
||||
elif order['side'] == 'sell':
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
|
||||
def handle_cancel_buy(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||
"""
|
||||
Buy cancel - cancel order
|
||||
:return: True if order was fully cancelled
|
||||
"""
|
||||
was_trade_fully_canceled = False
|
||||
|
||||
# Cancelled orders may have the status of 'canceled' or 'closed'
|
||||
if order['status'] not in ('canceled', 'closed'):
|
||||
reason = "cancelled due to timeout"
|
||||
reason = constants.CANCEL_REASON['TIMEOUT']
|
||||
corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
|
||||
trade.amount)
|
||||
else:
|
||||
# Order was cancelled already, so we can reuse the existing dict
|
||||
corder = order
|
||||
reason = "cancelled on exchange"
|
||||
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
||||
|
||||
logger.info('Buy order %s for %s.', reason, trade)
|
||||
|
||||
|
@ -921,43 +945,45 @@ class FreqtradeBot:
|
|||
# if trade is not partially completed, just delete the trade
|
||||
Trade.session.delete(trade)
|
||||
Trade.session.flush()
|
||||
return True
|
||||
was_trade_fully_canceled = True
|
||||
else:
|
||||
# if trade is partially complete, edit the stake details for the trade
|
||||
# and close the order
|
||||
# cancel_order may not contain the full order dict, so we need to fallback
|
||||
# to the order dict aquired before cancelling.
|
||||
# we need to fall back to the values from order if corder does not contain these keys.
|
||||
trade.amount = filled_amount
|
||||
trade.stake_amount = trade.amount * trade.open_rate
|
||||
self.update_trade_state(trade, corder, trade.amount)
|
||||
|
||||
# if trade is partially complete, edit the stake details for the trade
|
||||
# and close the order
|
||||
# cancel_order may not contain the full order dict, so we need to fallback
|
||||
# to the order dict aquired before cancelling.
|
||||
# we need to fall back to the values from order if corder does not contain these keys.
|
||||
trade.amount = filled_amount
|
||||
trade.stake_amount = trade.amount * trade.open_rate
|
||||
self.update_trade_state(trade, corder, trade.amount)
|
||||
trade.open_order_id = None
|
||||
logger.info('Partial buy order timeout for %s.', trade)
|
||||
self.rpc.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'status': f'Remaining buy order for {trade.pair} cancelled due to timeout'
|
||||
})
|
||||
|
||||
trade.open_order_id = None
|
||||
logger.info('Partial buy order timeout for %s.', trade)
|
||||
self.rpc.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'status': f'Remaining buy order for {trade.pair} cancelled due to timeout'
|
||||
})
|
||||
return False
|
||||
self.wallets.update()
|
||||
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'])
|
||||
return was_trade_fully_canceled
|
||||
|
||||
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> str:
|
||||
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||
"""
|
||||
Sell timeout - cancel order and update trade
|
||||
Sell cancel - cancel order and update trade
|
||||
:return: Reason for cancel
|
||||
"""
|
||||
# if trade is not partially completed, just cancel the trade
|
||||
# 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):
|
||||
reason = "cancelled due to timeout"
|
||||
try:
|
||||
# if trade is not partially completed, just delete the trade
|
||||
# if trade is not partially completed, just delete the order
|
||||
self.exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||
except InvalidOrderException:
|
||||
logger.exception(f"Could not cancel sell order {trade.open_order_id}")
|
||||
return 'error cancelling order'
|
||||
logger.info('Sell order %s for %s.', reason, trade)
|
||||
else:
|
||||
reason = "cancelled on exchange"
|
||||
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
||||
logger.info('Sell order %s for %s.', reason, trade)
|
||||
|
||||
trade.close_rate = None
|
||||
|
@ -967,11 +993,17 @@ class FreqtradeBot:
|
|||
trade.close_date = None
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
else:
|
||||
# TODO: figure out how to handle partially complete sell orders
|
||||
reason = constants.CANCEL_REASON['PARTIALLY_FILLED']
|
||||
|
||||
return reason
|
||||
|
||||
# TODO: figure out how to handle partially complete sell orders
|
||||
return 'partially filled - keeping order open'
|
||||
self.wallets.update()
|
||||
self._notify_sell_cancel(
|
||||
trade,
|
||||
order_type=self.strategy.order_types['sell'],
|
||||
reason=reason
|
||||
)
|
||||
return reason
|
||||
|
||||
def _safe_sell_amount(self, pair: str, amount: float) -> float:
|
||||
"""
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"fiat_display_currency": "{{ fiat_display_currency }}",
|
||||
"ticker_interval": "{{ ticker_interval }}",
|
||||
"dry_run": {{ dry_run | lower }},
|
||||
"cancel_open_orders_on_exit": false,
|
||||
"unfilledtimeout": {
|
||||
"buy": 10,
|
||||
"sell": 30
|
||||
|
|
|
@ -131,8 +131,7 @@ class Worker:
|
|||
return result
|
||||
|
||||
def _process_stopped(self) -> None:
|
||||
# Maybe do here something in the future...
|
||||
pass
|
||||
self.freqtrade.process_stopped()
|
||||
|
||||
def _process_running(self) -> None:
|
||||
try:
|
||||
|
|
|
@ -249,6 +249,7 @@ def default_conf(testdatadir):
|
|||
"fiat_display_currency": "USD",
|
||||
"ticker_interval": '5m',
|
||||
"dry_run": True,
|
||||
"cancel_open_orders_on_exit": False,
|
||||
"minimal_roi": {
|
||||
"40": 0.0,
|
||||
"30": 0.01,
|
||||
|
|
|
@ -11,7 +11,7 @@ import arrow
|
|||
import pytest
|
||||
import requests
|
||||
|
||||
from freqtrade.constants import MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT
|
||||
from freqtrade.constants import MATH_CLOSE_PREC, UNLIMITED_STAKE_AMOUNT, CANCEL_REASON
|
||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
||||
OperationalException, TemporaryError)
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
|
@ -22,7 +22,7 @@ from freqtrade.strategy.interface import SellCheckTuple, SellType
|
|||
from freqtrade.worker import Worker
|
||||
from tests.conftest import (get_patched_freqtradebot, get_patched_worker,
|
||||
log_has, log_has_re, patch_edge, patch_exchange,
|
||||
patch_get_signal, patch_wallet, patch_whitelist)
|
||||
patch_get_signal, patch_wallet, patch_whitelist, create_mock_trades)
|
||||
|
||||
|
||||
def patch_RPCManager(mocker) -> MagicMock:
|
||||
|
@ -48,13 +48,31 @@ def test_freqtradebot_state(mocker, default_conf, markets) -> None:
|
|||
assert freqtrade.state is State.STOPPED
|
||||
|
||||
|
||||
def test_cleanup(mocker, default_conf, caplog) -> None:
|
||||
mock_cleanup = MagicMock()
|
||||
mocker.patch('freqtrade.persistence.cleanup', mock_cleanup)
|
||||
def test_process_stopped(mocker, default_conf) -> None:
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
coo_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders')
|
||||
freqtrade.process_stopped()
|
||||
assert coo_mock.call_count == 0
|
||||
|
||||
default_conf['cancel_open_orders_on_exit'] = True
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
freqtrade.process_stopped()
|
||||
assert coo_mock.call_count == 1
|
||||
|
||||
|
||||
def test_bot_cleanup(mocker, default_conf, caplog) -> None:
|
||||
mock_cleanup = mocker.patch('freqtrade.persistence.cleanup')
|
||||
coo_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.cancel_all_open_orders')
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
freqtrade.cleanup()
|
||||
assert log_has('Cleaning up modules ...', caplog)
|
||||
assert mock_cleanup.call_count == 1
|
||||
assert coo_mock.call_count == 0
|
||||
|
||||
freqtrade.config['cancel_open_orders_on_exit'] = True
|
||||
freqtrade.cleanup()
|
||||
assert coo_mock.call_count == 1
|
||||
|
||||
|
||||
def test_order_dict_dry_run(default_conf, mocker, caplog) -> None:
|
||||
|
@ -2284,8 +2302,8 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
|||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
handle_timedout_limit_buy=MagicMock(),
|
||||
handle_timedout_limit_sell=MagicMock(),
|
||||
handle_cancel_buy=MagicMock(),
|
||||
handle_cancel_sell=MagicMock(),
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
|
@ -2305,50 +2323,54 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
|||
caplog)
|
||||
|
||||
|
||||
def test_handle_timedout_limit_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order)
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 2
|
||||
assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
limit_buy_order['filled'] = 2
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order', side_effect=InvalidOrderException)
|
||||
assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
|
||||
indirect=['limit_buy_order_canceled_empty'])
|
||||
def test_handle_timedout_limit_buy_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = mocker.patch(
|
||||
'freqtrade.exchange.Exchange.cancel_order_with_result',
|
||||
return_value=limit_buy_order_canceled_empty)
|
||||
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_buy_cancel')
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
Trade.session = MagicMock()
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order_canceled_empty)
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
|
||||
assert nofiy_mock.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cancelorder', [
|
||||
|
@ -2357,8 +2379,8 @@ def test_handle_timedout_limit_buy_exchanges(mocker, caplog, default_conf,
|
|||
'String Return value',
|
||||
123
|
||||
])
|
||||
def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=cancelorder)
|
||||
|
@ -2368,23 +2390,24 @@ def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_
|
|||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
|
||||
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 1.0
|
||||
assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
||||
def test_handle_cancel_sell_limit(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
|
@ -2394,21 +2417,23 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
|||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_sell_cancel = MagicMock()
|
||||
|
||||
trade = MagicMock()
|
||||
order = {'remaining': 1,
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
assert freqtrade.handle_timedout_limit_sell(trade, order)
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
order['amount'] = 2
|
||||
assert (freqtrade.handle_timedout_limit_sell(trade, order)
|
||||
== 'partially filled - keeping order open')
|
||||
assert (freqtrade.handle_cancel_sell(trade, order, reason)
|
||||
== CANCEL_REASON['PARTIALLY_FILLED'])
|
||||
# Assert cancel_order was not called (callcount remains unchanged)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_timedout_limit_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
|
@ -2417,10 +2442,11 @@ def test_handle_timedout_limit_sell_cancel_exception(mocker, default_conf) -> No
|
|||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade = MagicMock()
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
order = {'remaining': 1,
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
assert freqtrade.handle_timedout_limit_sell(trade, order) == 'error cancelling order'
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
||||
|
||||
|
||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
|
@ -3919,3 +3945,20 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order,
|
|||
assert log_has_re(r"Unable to create trade for XRP/BTC: "
|
||||
r"Available balance \(0.0 BTC\) is lower than stake amount \(0.001 BTC\)",
|
||||
caplog)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limit_sell_order):
|
||||
default_conf['cancel_open_orders_on_exit'] = True
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order',
|
||||
side_effect=[DependencyException(), limit_sell_order, limit_buy_order])
|
||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy')
|
||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 3
|
||||
freqtrade.cancel_all_open_orders()
|
||||
assert buy_mock.call_count == 1
|
||||
assert sell_mock.call_count == 1
|
||||
|
|
Loading…
Reference in New Issue
Block a user