From 096c69dc4f157558f3dd0d8798032722f5f16cb1 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 2 Oct 2019 03:27:17 +0300 Subject: [PATCH 1/9] Refactor Freqtradebot --- freqtrade/freqtradebot.py | 60 ++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3f7eab27a..024e09c96 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1,7 +1,6 @@ """ Freqtrade is the main module of this bot. It contains the class Freqtrade() """ - import copy import logging import traceback @@ -135,12 +134,11 @@ class FreqtradeBot: self.strategy.informative_pairs()) # First process current opened trades - for trade in trades: - self.process_maybe_execute_sell(trade) + self.process_maybe_execute_sells(trades) # Then looking for buy opportunities if len(trades) < self.config['max_open_trades']: - self.process_maybe_execute_buy() + self.process_maybe_execute_buys() if 'unfilledtimeout' in self.config: # Check and handle any timed out open orders @@ -262,11 +260,10 @@ class FreqtradeBot: Checks pairs as long as the open trade count is below `max_open_trades`. :return: True if at least one trade has been created. """ - interval = self.strategy.ticker_interval whitelist = copy.deepcopy(self.active_pair_whitelist) if not whitelist: - logger.warning("Whitelist is empty.") + logger.info("Active pair whitelist is empty.") return False # Remove currently opened and latest pairs from whitelist @@ -276,7 +273,8 @@ class FreqtradeBot: logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: - logger.info("No currency pair in whitelist, but checking to sell open trades.") + logger.info("No currency pair in active pair whitelist, " + "but checking to sell open trades.") return False buycount = 0 @@ -285,8 +283,10 @@ class FreqtradeBot: if self.strategy.is_pair_locked(_pair): logger.info(f"Pair {_pair} is currently locked.") continue + (buy, sell) = self.strategy.get_signal( - _pair, interval, self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval)) + _pair, self.strategy.ticker_interval, + self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval)) if buy and not sell and len(Trade.get_open_trades()) < self.config['max_open_trades']: stake_amount = self._get_trade_stake_amount(_pair) @@ -431,10 +431,9 @@ class FreqtradeBot: return True - def process_maybe_execute_buy(self) -> None: + def process_maybe_execute_buys(self) -> None: """ - Tries to execute a buy trade in a safe way - :return: True if executed + Tries to execute buy trades in a safe way """ try: # Create entity and execute trade @@ -443,33 +442,28 @@ class FreqtradeBot: except DependencyException as exception: logger.warning('Unable to create trade: %s', exception) - def process_maybe_execute_sell(self, trade: Trade) -> bool: + def process_maybe_execute_sells(self, trades: List[Any]) -> None: """ - Tries to execute a sell trade - :return: True if executed + Tries to execute sell trades in a safe way """ - try: - self.update_trade_state(trade) + for trade in trades: + try: + self.update_trade_state(trade) - if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open: - result = self.handle_stoploss_on_exchange(trade) - if result: - self.wallets.update() - return result + if trade.is_open: + result = False + if self.strategy.order_types.get('stoploss_on_exchange'): + result = self.handle_stoploss_on_exchange(trade) + elif trade.open_order_id is None: + # Check if we can sell our current pair + result = self.handle_trade(trade) - if trade.is_open and trade.open_order_id is None: - # Check if we can sell our current pair - result = self.handle_trade(trade) + # Updating wallets if any trade occured + if result: + self.wallets.update() - # Updating wallets if any trade occured - if result: - self.wallets.update() - - return result - - except DependencyException as exception: - logger.warning('Unable to sell trade: %s', exception) - return False + except DependencyException as exception: + logger.warning('Unable to sell trade: %s', exception) def get_real_amount(self, trade: Trade, order: Dict) -> float: """ From 15aae8a58c78b7a70719f0747c7e49d418d2b219 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 2 Oct 2019 03:27:42 +0300 Subject: [PATCH 2/9] Tests adjusted --- tests/test_freqtradebot.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index ee28f2e58..5fa481604 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -655,7 +655,8 @@ def test_create_trades_no_pairs_let(default_conf, ticker, limit_buy_order, fee, assert freqtrade.create_trades() assert not freqtrade.create_trades() - assert log_has("No currency pair in whitelist, but checking to sell open trades.", caplog) + assert log_has("No currency pair in active pair whitelist, " + "but checking to sell open trades.", caplog) def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee, @@ -674,7 +675,7 @@ def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_ord patch_get_signal(freqtrade) assert not freqtrade.create_trades() - assert log_has("Whitelist is empty.", caplog) + assert log_has("Active pair whitelist is empty.", caplog) def test_create_trades_no_signal(default_conf, fee, mocker) -> None: @@ -1057,8 +1058,9 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None trade.open_order_id = None trade.stoploss_order_id = None trade.is_open = True + trades = [trade] - freqtrade.process_maybe_execute_sell(trade) + freqtrade.process_maybe_execute_sells(trades) assert trade.stoploss_order_id == '13434334' assert stoploss_limit.call_count == 1 assert trade.is_open is True @@ -1518,26 +1520,26 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog, stop_price=0.00002344 * 0.99) -def test_process_maybe_execute_buy(mocker, default_conf, caplog) -> None: +def test_process_maybe_execute_buys(mocker, default_conf, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trades', MagicMock(return_value=False)) - freqtrade.process_maybe_execute_buy() + freqtrade.process_maybe_execute_buys() assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog) -def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> None: +def test_process_maybe_execute_buys_exception(mocker, default_conf, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch( 'freqtrade.freqtradebot.FreqtradeBot.create_trades', MagicMock(side_effect=DependencyException) ) - freqtrade.process_maybe_execute_buy() + freqtrade.process_maybe_execute_buys() assert log_has('Unable to create trade: ', caplog) -def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplog) -> None: +def test_process_maybe_execute_sells(mocker, default_conf, limit_buy_order, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) @@ -1549,7 +1551,8 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo trade = MagicMock() trade.open_order_id = '123' trade.open_fee = 0.001 - assert not freqtrade.process_maybe_execute_sell(trade) + trades = [trade] + assert not freqtrade.process_maybe_execute_sells(trades) # Test amount not modified by fee-logic assert not log_has( 'Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(trade), caplog @@ -1557,24 +1560,25 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81) # test amount modified by fee-logic - assert not freqtrade.process_maybe_execute_sell(trade) + assert not freqtrade.process_maybe_execute_sells(trades) -def test_process_maybe_execute_sell_exception(mocker, default_conf, - limit_buy_order, caplog) -> None: +def test_process_maybe_execute_sells_exception(mocker, default_conf, + limit_buy_order, caplog) -> None: freqtrade = get_patched_freqtradebot(mocker, default_conf) mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order) trade = MagicMock() trade.open_order_id = '123' trade.open_fee = 0.001 + trades = [trade] # Test raise of DependencyException exception mocker.patch( 'freqtrade.freqtradebot.FreqtradeBot.update_trade_state', side_effect=DependencyException() ) - freqtrade.process_maybe_execute_sell(trade) + freqtrade.process_maybe_execute_sells(trades) assert log_has('Unable to sell trade: ', caplog) @@ -2448,8 +2452,9 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, trade = Trade.query.first() assert trade + trades = [trade] - freqtrade.process_maybe_execute_sell(trade) + freqtrade.process_maybe_execute_sells(trades) # Increase the price and sell it mocker.patch.multiple( @@ -2498,7 +2503,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, # Create some test data freqtrade.create_trades() trade = Trade.query.first() - freqtrade.process_maybe_execute_sell(trade) + trades = [trade] + freqtrade.process_maybe_execute_sells(trades) assert trade assert trade.stoploss_order_id == '123' assert trade.open_order_id is None @@ -2526,7 +2532,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, }) mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_limit_executed) - freqtrade.process_maybe_execute_sell(trade) + freqtrade.process_maybe_execute_sells(trades) assert trade.stoploss_order_id is None assert trade.is_open is False assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value From 89729aefe8e148490a595e6f0f5d1d573fcdee08 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 2 Oct 2019 18:38:00 +0300 Subject: [PATCH 3/9] Fix and improve process_maybe_execute_sells() --- freqtrade/freqtradebot.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 024e09c96..4fb0ce2d7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -446,25 +446,26 @@ class FreqtradeBot: """ Tries to execute sell trades in a safe way """ + result = False for trade in trades: try: self.update_trade_state(trade) - if trade.is_open: - result = False - if self.strategy.order_types.get('stoploss_on_exchange'): - result = self.handle_stoploss_on_exchange(trade) - elif trade.open_order_id is None: - # Check if we can sell our current pair - result = self.handle_trade(trade) - - # Updating wallets if any trade occured - if result: - self.wallets.update() + if (self.strategy.order_types.get('stoploss_on_exchange') and + self.handle_stoploss_on_exchange(trade)): + result = True + continue + # Check if we can sell our current pair + if trade.open_order_id is None and self.handle_trade(trade): + result = True except DependencyException as exception: logger.warning('Unable to sell trade: %s', exception) + # Updating wallets if any trade occured + if result: + self.wallets.update() + def get_real_amount(self, trade: Trade, order: Dict) -> float: """ Get real amount for the trade @@ -569,7 +570,7 @@ class FreqtradeBot: :return: True if trade has been sold, False otherwise """ if not trade.is_open: - raise ValueError(f'Attempt to handle closed trade: {trade}') + raise DependencyException(f'Attempt to handle closed trade: {trade}') logger.debug('Handling %s ...', trade) From 4b29c4cdbf222dc9abd3b63f9a632226fede4a37 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Wed, 2 Oct 2019 19:08:49 +0300 Subject: [PATCH 4/9] Test for handling closed trade adjusted --- tests/test_freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5fa481604..eb854e56b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1915,7 +1915,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, trade.update(limit_sell_order) assert trade.is_open is False - with pytest.raises(ValueError, match=r'.*closed trade.*'): + with pytest.raises(DependencyException, match=r'.*closed trade.*'): freqtrade.handle_trade(trade) From 9ee7e28ef8d98cbbb3d3b827089d45d80cc6bb64 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Oct 2019 06:23:58 +0200 Subject: [PATCH 5/9] Clean up some mocks --- tests/test_freqtradebot.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index eb854e56b..18fd229eb 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2422,13 +2422,6 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, default_conf['exchange']['name'] = 'binance' rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_ticker=ticker, - get_fee=fee, - markets=PropertyMock(return_value=markets) - ) - stoploss_limit = MagicMock(return_value={ 'id': 123, 'info': { @@ -2437,11 +2430,16 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, }) cancel_order = MagicMock(return_value=True) - - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit) - mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=ticker, + get_fee=fee, + markets=PropertyMock(return_value=markets), + symbol_amount_prec=lambda s, x, y: y, + symbol_price_prec=lambda s, x, y: y, + stoploss_limit=stoploss_limit, + cancel_order=cancel_order, + ) freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.order_types['stoploss_on_exchange'] = True @@ -2482,7 +2480,9 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, 'freqtrade.exchange.Exchange', get_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), + symbol_amount_prec=lambda s, x, y: y, + symbol_price_prec=lambda s, x, y: y, ) stoploss_limit = MagicMock(return_value={ @@ -2492,8 +2492,6 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, } }) - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Binance.stoploss_limit', stoploss_limit) freqtrade = FreqtradeBot(default_conf) From 1f4e5b17b74e0aafb6c8179907caa8a25f2f45fb Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Oct 2019 06:37:25 +0200 Subject: [PATCH 6/9] Add basic test for execute sells_multiple logic --- tests/test_freqtradebot.py | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 18fd229eb..128411d96 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2537,6 +2537,50 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, assert rpc_mock.call_count == 2 +def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, + ticker, fee, + limit_buy_order, + markets, mocker) -> None: + default_conf['max_open_trades'] = 3 + default_conf['exchange']['name'] = 'binance' + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_ticker=ticker, + get_fee=fee, + markets=PropertyMock(return_value=markets), + symbol_amount_prec=lambda s, x, y: y, + symbol_price_prec=lambda s, x, y: y, + ) + # Sell first trade + stoploss_mock = MagicMock(side_effect=[True, False, False]) + + # Sell 3rd trade (not called for the first trade) + handle_trade_mock = MagicMock(side_effect=[False, True]) + wallets_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.freqtradebot.FreqtradeBot', + handle_stoploss_on_exchange=stoploss_mock, + handle_trade=handle_trade_mock, + ) + mocker.patch("freqtrade.wallets.Wallets.update", wallets_mock) + + freqtrade = FreqtradeBot(default_conf) + freqtrade.strategy.order_types['stoploss_on_exchange'] = True + patch_get_signal(freqtrade) + + # Create some test data + freqtrade.create_trades() + wallets_mock.reset_mock() + + trades = Trade.query.all() + + freqtrade.process_maybe_execute_sells(trades) + assert stoploss_mock.call_count == 3 + assert handle_trade_mock.call_count == 2 + assert wallets_mock.call_count == 1 + def test_execute_sell_market_order(default_conf, ticker, fee, ticker_sell_up, markets, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 38f184e50de65485a7a058a73cf1fca2d8865719 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Oct 2019 06:54:15 +0200 Subject: [PATCH 7/9] Update test to not mock stoploss_on_exchange --- tests/test_freqtradebot.py | 54 ++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 128411d96..d60d19937 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2545,6 +2545,39 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, default_conf['exchange']['name'] = 'binance' patch_RPCManager(mocker) patch_exchange(mocker) + + stoploss_limit = { + 'id': 123, + 'info': { + 'foo': 'bar' + } + } + stoploss_order_open = { + "id": "123", + "timestamp": 1542707426845, + "datetime": "2018-11-20T09:50:26.845Z", + "lastTradeTimestamp": None, + "symbol": "BTC/USDT", + "type": "stop_loss_limit", + "side": "sell", + "price": 1.08801, + "amount": 90.99181074, + "cost": 0.0, + "average": 0.0, + "filled": 0.0, + "remaining": 0.0, + "status": "open", + "fee": None, + "trades": None + } + stoploss_order_closed = stoploss_order_open.copy() + stoploss_order_closed['status'] = 'closed' + # Sell first trade based on stoploss, keep 2nd and 3rd trade open + stoploss_order_mock = MagicMock( + side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open]) + create_sl_mock = MagicMock(return_value=True) + + mocker.patch('freqtrade.exchange.Binance.stoploss_limit', stoploss_limit) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, @@ -2552,17 +2585,17 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, markets=PropertyMock(return_value=markets), symbol_amount_prec=lambda s, x, y: y, symbol_price_prec=lambda s, x, y: y, + get_order=stoploss_order_mock, ) - # Sell first trade - stoploss_mock = MagicMock(side_effect=[True, False, False]) - # Sell 3rd trade (not called for the first trade) handle_trade_mock = MagicMock(side_effect=[False, True]) wallets_mock = MagicMock() mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', - handle_stoploss_on_exchange=stoploss_mock, handle_trade=handle_trade_mock, + create_stoploss_order=create_sl_mock, + update_trade_state=MagicMock(), + _notify_sell=MagicMock(), ) mocker.patch("freqtrade.wallets.Wallets.update", wallets_mock) @@ -2575,12 +2608,23 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, wallets_mock.reset_mock() trades = Trade.query.all() + # Make sure stoploss-order is open and trade is bought (since we mock update_trade_state) + for trade in trades: + trade.stoploss_order_id = 3 + trade.open_order_id = None freqtrade.process_maybe_execute_sells(trades) - assert stoploss_mock.call_count == 3 assert handle_trade_mock.call_count == 2 assert wallets_mock.call_count == 1 + trade = trades[0] + assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value + assert not trade.is_open + trade = trades[1] + assert not trade.sell_reason + assert trade.is_open + + def test_execute_sell_market_order(default_conf, ticker, fee, ticker_sell_up, markets, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 75252b6251ac766a14f6f2e8b591383c09dc8c16 Mon Sep 17 00:00:00 2001 From: hroff-1902 Date: Fri, 4 Oct 2019 02:32:48 +0300 Subject: [PATCH 8/9] Docstrings improved --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 4fb0ce2d7..2776b56c2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -433,7 +433,7 @@ class FreqtradeBot: def process_maybe_execute_buys(self) -> None: """ - Tries to execute buy trades in a safe way + Tries to execute buy orders for trades in a safe way """ try: # Create entity and execute trade @@ -444,7 +444,7 @@ class FreqtradeBot: def process_maybe_execute_sells(self, trades: List[Any]) -> None: """ - Tries to execute sell trades in a safe way + Tries to execute sell orders for trades in a safe way """ result = False for trade in trades: From 78381e9e7be618c89d1681f31f48df8b0918be29 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Oct 2019 14:47:37 +0200 Subject: [PATCH 9/9] Improve test to test full sell cycle --- tests/test_freqtradebot.py | 41 ++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index d60d19937..8c6bca653 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2541,6 +2541,13 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, limit_buy_order, markets, mocker) -> None: + """ + Tests workflow of selling stoploss_on_exchange. + Sells + * first trade as stoploss + * 2nd trade is kept + * 3rd trade is sold via sell-signal + """ default_conf['max_open_trades'] = 3 default_conf['exchange']['name'] = 'binance' patch_RPCManager(mocker) @@ -2548,9 +2555,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, stoploss_limit = { 'id': 123, - 'info': { - 'foo': 'bar' - } + 'info': {} } stoploss_order_open = { "id": "123", @@ -2575,8 +2580,12 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, # Sell first trade based on stoploss, keep 2nd and 3rd trade open stoploss_order_mock = MagicMock( side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open]) - create_sl_mock = MagicMock(return_value=True) - + # Sell 3rd trade (not called for the first trade) + should_sell_mock = MagicMock(side_effect=[ + SellCheckTuple(sell_flag=False, sell_type=SellType.NONE), + SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL)] + ) + cancel_order_mock = MagicMock() mocker.patch('freqtrade.exchange.Binance.stoploss_limit', stoploss_limit) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -2586,26 +2595,29 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, symbol_amount_prec=lambda s, x, y: y, symbol_price_prec=lambda s, x, y: y, get_order=stoploss_order_mock, + cancel_order=cancel_order_mock, ) - # Sell 3rd trade (not called for the first trade) - handle_trade_mock = MagicMock(side_effect=[False, True]) + wallets_mock = MagicMock() mocker.patch.multiple( 'freqtrade.freqtradebot.FreqtradeBot', - handle_trade=handle_trade_mock, - create_stoploss_order=create_sl_mock, + create_stoploss_order=MagicMock(return_value=True), update_trade_state=MagicMock(), _notify_sell=MagicMock(), ) + mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock) mocker.patch("freqtrade.wallets.Wallets.update", wallets_mock) freqtrade = FreqtradeBot(default_conf) freqtrade.strategy.order_types['stoploss_on_exchange'] = True + # Switch ordertype to market to close trade immediately + freqtrade.strategy.order_types['sell'] = 'market' patch_get_signal(freqtrade) # Create some test data freqtrade.create_trades() wallets_mock.reset_mock() + Trade.session = MagicMock() trades = Trade.query.all() # Make sure stoploss-order is open and trade is bought (since we mock update_trade_state) @@ -2614,16 +2626,25 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, trade.open_order_id = None freqtrade.process_maybe_execute_sells(trades) - assert handle_trade_mock.call_count == 2 + assert should_sell_mock.call_count == 2 + + # Only order for 3rd trade needs to be cancelled + assert cancel_order_mock.call_count == 1 + # Wallets should only be called once per sell cycle assert wallets_mock.call_count == 1 trade = trades[0] assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value assert not trade.is_open + trade = trades[1] assert not trade.sell_reason assert trade.is_open + trade = trades[2] + assert trade.sell_reason == SellType.SELL_SIGNAL.value + assert not trade.is_open + def test_execute_sell_market_order(default_conf, ticker, fee, ticker_sell_up, markets, mocker) -> None: