diff --git a/freqtrade/plugins/pairlist/PerformanceFilter.py b/freqtrade/plugins/pairlist/PerformanceFilter.py index d3196d3ae..5b02a47ab 100644 --- a/freqtrade/plugins/pairlist/PerformanceFilter.py +++ b/freqtrade/plugins/pairlist/PerformanceFilter.py @@ -60,6 +60,7 @@ class PerformanceFilter(IPairList): # Get pairlist from performance dataframe values list_df = pd.DataFrame({'pair': pairlist}) + list_df['prior_idx'] = list_df.index # Set initial value for pairs with no trades to 0 # Sort the list using: @@ -67,7 +68,7 @@ class PerformanceFilter(IPairList): # - then count (low to high, so as to favor same performance with fewer trades) # - then pair name alphametically sorted_df = list_df.merge(performance, on='pair', how='left')\ - .fillna(0).sort_values(by=['count', 'pair'], ascending=True)\ + .fillna(0).sort_values(by=['count', 'prior_idx'], ascending=True)\ .sort_values(by=['profit_ratio'], ascending=False) if self._min_profit is not None: removed = sorted_df[sorted_df['profit_ratio'] < self._min_profit] diff --git a/tests/conftest_trades_usdt.py b/tests/conftest_trades_usdt.py index 7093678e4..508e54f03 100644 --- a/tests/conftest_trades_usdt.py +++ b/tests/conftest_trades_usdt.py @@ -342,7 +342,8 @@ def mock_trade_usdt_7(fee): stake_amount=20.0, amount=2.0, amount_requested=2.0, - open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=5), + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), + close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=5), fee_open=fee.return_value, fee_close=fee.return_value, is_open=False, diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index da1bda066..52158a889 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -740,6 +740,33 @@ def test_PerformanceFilter_lookback(mocker, default_conf_usdt, fee, caplog) -> N assert pm.whitelist == ['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'] +@pytest.mark.usefixtures("init_persistence") +def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog) -> None: + default_conf_usdt['exchange']['pair_whitelist'].extend(['ADA/USDT', 'ETC/USDT']) + default_conf_usdt['pairlists'] = [ + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "PerformanceFilter", "minutes": 60, } + ] + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf_usdt) + pm = PairListManager(exchange, default_conf_usdt) + pm.refresh_pairlist() + + assert pm.whitelist == ['ETH/USDT', 'LTC/USDT', 'XRP/USDT', + 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'ETC/USDT'] + + with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: + create_mock_trades_usdt(fee) + pm.refresh_pairlist() + assert pm.whitelist == ['XRP/USDT', 'ETC/USDT', 'ETH/USDT', + 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'LTC/USDT'] + # assert log_has_re(r'Removing pair .* since .* is below .*', caplog) + + # Move to "outside" of lookback window, so original sorting is restored. + t.move_to("2021-09-01 07:00:00 +00:00") + pm.refresh_pairlist() + assert pm.whitelist == ['ETH/USDT', 'LTC/USDT', 'XRP/USDT', + 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'ETC/USDT'] def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: @@ -1170,13 +1197,13 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): {'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 2}, {'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 100}], ['TKN/BTC', 'ETH/BTC', 'LTC/BTC']), - # Tie in performance and count, broken by alphabetical sort + # Tie in performance and count, broken by prior sorting sort ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], [{'pair': 'LTC/BTC', 'profit_ratio': -0.0501, 'count': 1}, {'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 1}, {'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 1}], - ['ETH/BTC', 'LTC/BTC', 'TKN/BTC']), + ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), ]) def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance, allowlist_result, tickers, markets, ohlcv_history_list):