From c303d47f26984626382f15db6c2bd77731dcfcd4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 23 Aug 2023 15:16:14 +0200 Subject: [PATCH 01/12] Ensure stop_duration is converted to int closes #9099 --- freqtrade/plugins/protections/iprotection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 8e1589217..6057cbc29 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -42,7 +42,7 @@ class IProtection(LoggingMixin, ABC): self._stop_duration = (tf_in_min * self._stop_duration_candles) else: self._stop_duration_candles = None - self._stop_duration = protection_config.get('stop_duration', 60) + self._stop_duration = int(protection_config.get('stop_duration', 60)) if 'lookback_period_candles' in protection_config: self._lookback_period_candles = int(protection_config.get('lookback_period_candles', 1)) self._lookback_period = tf_in_min * self._lookback_period_candles From e1e90112baa35e67ed68b2860e9d9a9b9b8accbe Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 07:13:24 +0200 Subject: [PATCH 02/12] conftest_usdt trades - align open orders --- tests/conftest_trades_usdt.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/conftest_trades_usdt.py b/tests/conftest_trades_usdt.py index d54a416ef..fc4776148 100644 --- a/tests/conftest_trades_usdt.py +++ b/tests/conftest_trades_usdt.py @@ -36,13 +36,13 @@ def mock_order_usdt_1_exit(is_short: bool): return { 'id': f'prod_exit_1_{direc(is_short)}', 'symbol': 'LTC/USDT', - 'status': 'closed', + 'status': 'open', 'side': exit_side(is_short), 'type': 'limit', 'price': 8.0, 'amount': 2.0, - 'filled': 2.0, - 'remaining': 0.0, + 'filled': 0.0, + 'remaining': 2.0, } @@ -96,13 +96,13 @@ def mock_order_usdt_2_exit(is_short: bool): return { 'id': f'12366_{direc(is_short)}', 'symbol': 'NEO/USDT', - 'status': 'closed', + 'status': 'open', 'side': exit_side(is_short), 'type': 'limit', 'price': 2.05, 'amount': 100.0, - 'filled': 100.0, - 'remaining': 0.0, + 'filled': 0.0, + 'remaining': 100.0, } @@ -378,7 +378,7 @@ def mock_trade_usdt_7(fee, is_short: bool): open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17), open_rate=2.0, exchange='binance', - open_order_id=f'1234_{direc(is_short)}', + open_order_id=None, strategy='StrategyTestV2', timeframe=5, is_short=is_short, From 94864a6ab38ff7e0674d7c0d4a60e1d758a053b0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 07:28:55 +0200 Subject: [PATCH 03/12] Fix bad open_order_id assignment in test --- tests/conftest_trades.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 9642435e5..987fd9f40 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -412,7 +412,7 @@ def short_trade(fee): # close_profit_abs=-0.6925113200000013, exchange='binance', is_open=True, - open_order_id='dry_run_exit_short_12345', + open_order_id=None, strategy='DefaultStrategy', timeframe=5, exit_reason='sell_signal', From a36e13183884fd5766a2fca20ea2091e5529ab32 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 07:29:50 +0200 Subject: [PATCH 04/12] Fix more conftest trades --- tests/conftest_trades.py | 1 - tests/rpc/test_rpc_apiserver.py | 2 +- tests/test_freqtradebot.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 987fd9f40..056d055c2 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -103,7 +103,6 @@ def mock_trade_2(fee, is_short: bool): close_profit_abs=-0.005584127 if is_short else 0.000584127, exchange='binance', is_open=False, - open_order_id=f'dry_run_sell_{direc(is_short)}_12345', strategy='StrategyTestV3', timeframe=5, enter_tag='TEST1', diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index f9533e893..8810cfc2c 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -706,7 +706,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all()) rc = client_delete(client, f"{BASE_URI}/trades/2") assert_response(rc) - assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 2 open orders.' + assert rc.json()['result_msg'] == 'Deleted trade 2. Closed 1 open orders.' assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all()) assert stoploss_mock.call_count == 1 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index f595788dd..958be6e24 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5329,8 +5329,8 @@ def test_sync_wallet_dry_run(mocker, default_conf_usdt, ticker_usdt, fee, limit_ @pytest.mark.usefixtures("init_persistence") @pytest.mark.parametrize("is_short,buy_calls,sell_calls", [ - (False, 1, 2), - (True, 1, 2), + (False, 1, 1), + (True, 1, 1), ]) def test_cancel_all_open_orders(mocker, default_conf_usdt, fee, limit_order, limit_order_open, is_short, buy_calls, sell_calls): From cfe1187cd997f67e43226e047956967019e291c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 17:38:56 +0200 Subject: [PATCH 05/12] Fix missed Test --- tests/rpc/test_rpc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 3bc725f3a..d97222adc 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -363,9 +363,8 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): res = rpc._rpc_delete('2') assert isinstance(res, dict) - assert cancel_mock.call_count == 1 assert stoploss_mock.call_count == 1 - assert res['cancel_order_count'] == 2 + assert res['cancel_order_count'] == 1 stoploss_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException) From 0f73e38f98bd0286970e0818b70d574121a45f0f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 17:44:48 +0200 Subject: [PATCH 06/12] Improve docstring for "select_filled_orders". --- freqtrade/persistence/trade_model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index 7b17bef8d..275cccaf8 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -1032,7 +1032,8 @@ class LocalTrade: def select_filled_orders(self, order_side: Optional[str] = None) -> List['Order']: """ - Finds filled orders for this orderside. + Finds filled orders for this order side. + Will not return open orders which already partially filled. :param order_side: Side of the order (either 'buy', 'sell', or None) :return: array of Order objects """ From 0cc7039232017588b3d24977704797c5078774cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 17:53:46 +0200 Subject: [PATCH 07/12] Fix mock trade 1 status --- tests/conftest_trades.py | 6 +++--- tests/persistence/test_persistence.py | 31 ++++++++++++++------------- tests/test_freqtradebot.py | 13 +++++------ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 056d055c2..e50ed0e59 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -22,15 +22,15 @@ def mock_order_1(is_short: bool): return { 'id': f'1234_{direc(is_short)}', 'symbol': 'ETH/BTC', - 'status': 'closed', + 'status': 'open', 'side': entry_side(is_short), 'type': 'limit', 'price': 0.123, 'average': 0.123, 'amount': 123.0, - 'filled': 123.0, + 'filled': 50.0, 'cost': 15.129, - 'remaining': 0.0, + 'remaining': 123.0 - 50.0, } diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 958db8c72..5adaba1d6 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -1969,9 +1969,9 @@ def test_select_order(fee, is_short): # Open buy order, no sell order order = trades[0].select_order(trades[0].entry_side, True) - assert order is None - order = trades[0].select_order(trades[0].entry_side, False) assert order is not None + order = trades[0].select_order(trades[0].entry_side, False) + assert order is None order = trades[0].select_order(trades[0].exit_side, None) assert order is None @@ -2442,7 +2442,16 @@ def test_select_filled_orders(fee): # Closed buy order, no sell order orders = trades[0].select_filled_orders('buy') + assert isinstance(orders, list) + assert len(orders) == 0 + + orders = trades[0].select_filled_orders('sell') assert orders is not None + assert len(orders) == 0 + + # closed buy order, and closed sell order + orders = trades[1].select_filled_orders('buy') + assert isinstance(orders, list) assert len(orders) == 1 order = orders[0] assert order.amount > 0 @@ -2450,33 +2459,25 @@ def test_select_filled_orders(fee): assert order.side == 'buy' assert order.ft_order_side == 'buy' assert order.status == 'closed' - orders = trades[0].select_filled_orders('sell') - assert orders is not None - assert len(orders) == 0 - - # closed buy order, and closed sell order - orders = trades[1].select_filled_orders('buy') - assert orders is not None - assert len(orders) == 1 orders = trades[1].select_filled_orders('sell') - assert orders is not None + assert isinstance(orders, list) assert len(orders) == 1 # Has open buy order orders = trades[3].select_filled_orders('buy') - assert orders is not None + assert isinstance(orders, list) assert len(orders) == 0 orders = trades[3].select_filled_orders('sell') - assert orders is not None + assert isinstance(orders, list) assert len(orders) == 0 # Open sell order orders = trades[4].select_filled_orders('buy') - assert orders is not None + assert isinstance(orders, list) assert len(orders) == 1 orders = trades[4].select_filled_orders('sell') - assert orders is not None + assert isinstance(orders, list) assert len(orders) == 0 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 958be6e24..0d1d6d5c0 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5387,7 +5387,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s freqtrade.config['dry_run'] = False freqtrade.startup_update_open_orders() - assert len(Order.get_open_orders()) == 3 + assert len(Order.get_open_orders()) == 4 matching_buy_order = mock_order_4(is_short=is_short) matching_buy_order.update({ 'status': 'closed', @@ -5395,7 +5395,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s mocker.patch(f'{EXMS}.fetch_order', return_value=matching_buy_order) freqtrade.startup_update_open_orders() # Only stoploss and sell orders are kept open - assert len(Order.get_open_orders()) == 2 + assert len(Order.get_open_orders()) == 3 caplog.clear() mocker.patch(f'{EXMS}.fetch_order', side_effect=ExchangeError) @@ -5407,7 +5407,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s # Orders which are no longer found after X days should be assumed as canceled. freqtrade.startup_update_open_orders() assert log_has_re(r"Order is older than \d days.*", caplog) - assert hto_mock.call_count == 2 + assert hto_mock.call_count == 3 assert hto_mock.call_args_list[0][0][0]['status'] == 'canceled' assert hto_mock.call_args_list[1][0][0]['status'] == 'canceled' @@ -5561,14 +5561,15 @@ def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, is_short, cap caplog.clear() # No open order - trade = trades[0] + trade = trades[1] reset_open_orders(trade) assert trade.open_order_id is None assert trade.stoploss_order_id is None freqtrade.handle_insufficient_funds(trade) - order = mock_order_1(is_short=is_short) - assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog) + order = trade.orders[0] + assert log_has_re(r"Order Order(.*order_id=" + order.order_id + ".*) is no longer open.", + caplog) assert mock_fo.call_count == 0 assert mock_uts.call_count == 0 # No change to orderid - as update_trade_state is mocked From ffdb5fb790de8bdf59c76170d7723e0de9fb0740 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 18:06:17 +0200 Subject: [PATCH 08/12] Fix further tests after conftest_trades rework --- tests/rpc/test_rpc_apiserver.py | 8 ++++---- tests/rpc/test_rpc_telegram.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8810cfc2c..a85d15365 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -841,7 +841,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06, 'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2, 'profit_factor': 0.0, 'winrate': 0.0, 'expectancy': -0.0033695635, - 'expectancy_ratio': -1.0, 'trading_volume': 91.074, + 'expectancy_ratio': -1.0, 'trading_volume': 75.945, } ), ( @@ -857,7 +857,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0, 'profit_factor': None, 'winrate': 1.0, 'expectancy': 0.0003695635, - 'expectancy_ratio': 100, 'trading_volume': 91.074, + 'expectancy_ratio': 100, 'trading_volume': 75.945, } ), ( @@ -874,7 +874,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1, 'profit_factor': 0.02775724835771106, 'winrate': 0.5, 'expectancy': -0.0027145635000000003, 'expectancy_ratio': -0.48612137582114445, - 'trading_volume': 91.074, + 'trading_volume': 75.945, } ) ]) @@ -1125,7 +1125,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, assert_response(rc) resp_values = rc.json() assert len(resp_values) == 4 - assert resp_values[0]['profit_abs'] is None + assert resp_values[0]['profit_abs'] == 0.0 def test_api_version(botclient): diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 72d70bfb9..899bef20e 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -316,7 +316,7 @@ async def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> create_mock_trades(fee) trades = Trade.get_open_trades() - trade = trades[0] + trade = trades[3] # Average may be empty on some exchanges trade.orders[0].average = 0 trade.orders.append(Order( @@ -344,9 +344,9 @@ async def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 4 - msg = msg_mock.call_args_list[0][0][0] + msg = msg_mock.call_args_list[3][0][0] assert re.search(r'Number of Entries.*2', msg) - assert re.search(r'Number of Exits.*0', msg) + assert re.search(r'Number of Exits.*1', msg) assert re.search(r'Average Entry Price', msg) assert re.search(r'Order filled', msg) assert re.search(r'Close Date:', msg) is None From a740b9458f431cad65be82a24312dd4f0f0a2129 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 19:49:05 +0200 Subject: [PATCH 09/12] Fix remaining test after conftest_trade rework --- tests/test_freqtradebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 0d1d6d5c0..45df2b9cb 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5451,7 +5451,6 @@ def test_update_trades_without_assigned_fees(mocker, default_conf_usdt, fee, is_ side_effect=[ patch_with_fee(mock_order_2_sell(is_short=is_short)), patch_with_fee(mock_order_3_sell(is_short=is_short)), - patch_with_fee(mock_order_1(is_short=is_short)), patch_with_fee(mock_order_2(is_short=is_short)), patch_with_fee(mock_order_3(is_short=is_short)), patch_with_fee(mock_order_4(is_short=is_short)), From 67e3ce308be7fc64bbd768dc00be616f3c0da4a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 20:01:23 +0200 Subject: [PATCH 10/12] Remove now unused import --- tests/test_freqtradebot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 45df2b9cb..30bffe4e1 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -28,8 +28,7 @@ from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal, patch_wallet, patch_whitelist) -from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_1, - mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell, +from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell, mock_order_4, mock_order_5_stoploss, mock_order_6_sell) from tests.conftest_trades_usdt import mock_trade_usdt_4 From 9c4aca2b90970154e7729e87dc8fee9b78bd3a84 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 20:05:20 +0200 Subject: [PATCH 11/12] Improve download data debug output --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 9e168dcb7..d05965023 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2152,7 +2152,7 @@ class Exchange: except IndexError: logger.exception("Error loading %s. Result was %s.", pair, data) return pair, timeframe, candle_type, [], self._ohlcv_partial_candle - logger.debug("Done fetching pair %s, interval %s ...", pair, timeframe) + logger.debug("Done fetching pair %s, %s interval %s...", pair, candle_type, timeframe ) return pair, timeframe, candle_type, data, self._ohlcv_partial_candle except ccxt.NotSupported as e: From e5a88fdedad4f267f8e933b3e732b32685069fda Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Aug 2023 20:06:51 +0200 Subject: [PATCH 12/12] Fix stylistic issues --- freqtrade/exchange/exchange.py | 2 +- tests/test_freqtradebot.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d05965023..83a60b906 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2152,7 +2152,7 @@ class Exchange: except IndexError: logger.exception("Error loading %s. Result was %s.", pair, data) return pair, timeframe, candle_type, [], self._ohlcv_partial_candle - logger.debug("Done fetching pair %s, %s interval %s...", pair, candle_type, timeframe ) + logger.debug("Done fetching pair %s, %s interval %s...", pair, candle_type, timeframe) return pair, timeframe, candle_type, data, self._ohlcv_partial_candle except ccxt.NotSupported as e: diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 30bffe4e1..088c9ee5e 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -28,8 +28,9 @@ from tests.conftest import (EXMS, create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal, patch_wallet, patch_whitelist) -from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_2, mock_order_2_sell, mock_order_3, mock_order_3_sell, - mock_order_4, mock_order_5_stoploss, mock_order_6_sell) +from tests.conftest_trades import (MOCK_TRADE_COUNT, entry_side, exit_side, mock_order_2, + mock_order_2_sell, mock_order_3, mock_order_3_sell, mock_order_4, + mock_order_5_stoploss, mock_order_6_sell) from tests.conftest_trades_usdt import mock_trade_usdt_4