Merge pull request #9861 from freqtrade/feat/sort_volatility

Add sorting to volatility and RangeStability pairlists
This commit is contained in:
Matthias
2024-03-01 06:52:34 +01:00
committed by GitHub
5 changed files with 234 additions and 70 deletions
+4 -4
View File
@@ -142,8 +142,8 @@ def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=
return df
def generate_test_data(timeframe: str, size: int, start: str = '2020-07-05'):
np.random.seed(42)
def generate_test_data(timeframe: str, size: int, start: str = '2020-07-05', random_seed=42):
np.random.seed(random_seed)
base = np.random.normal(20, 2, size=size)
if timeframe == '1y':
@@ -174,9 +174,9 @@ def generate_test_data(timeframe: str, size: int, start: str = '2020-07-05'):
return df
def generate_test_data_raw(timeframe: str, size: int, start: str = '2020-07-05'):
def generate_test_data_raw(timeframe: str, size: int, start: str = '2020-07-05', random_seed=42):
""" Generates data in the ohlcv format used by ccxt """
df = generate_test_data(timeframe, size, start)
df = generate_test_data(timeframe, size, start, random_seed)
df['date'] = df.loc[:, 'date'].astype(np.int64) // 1000 // 1000
return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns)))
+106 -1
View File
@@ -19,7 +19,7 @@ from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist,
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.resolvers import PairListResolver
from freqtrade.util.datetime_helpers import dt_now
from tests.conftest import (EXMS, create_mock_trades_usdt, get_patched_exchange,
from tests.conftest import (EXMS, create_mock_trades_usdt, generate_test_data, get_patched_exchange,
get_patched_freqtradebot, log_has, log_has_re, num_log_has)
@@ -748,6 +748,104 @@ def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
assert log_has("PerformanceFilter is not available in this mode.", caplog)
def test_VolatilityFilter_error(mocker, whitelist_conf) -> None:
volatility_filter = {"method": "VolatilityFilter", "lookback_days": -1}
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, volatility_filter]
mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True))
exchange_mock = MagicMock()
exchange_mock.ohlcv_candle_limit = MagicMock(return_value=1000)
with pytest.raises(OperationalException,
match=r"VolatilityFilter requires lookback_days to be >= 1*"):
PairListManager(exchange_mock, whitelist_conf, MagicMock())
volatility_filter = {"method": "VolatilityFilter", "lookback_days": 2000}
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, volatility_filter]
with pytest.raises(OperationalException,
match=r"VolatilityFilter requires lookback_days to not exceed exchange max"):
PairListManager(exchange_mock, whitelist_conf, MagicMock())
volatility_filter = {"method": "VolatilityFilter", "sort_direction": "Random"}
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, volatility_filter]
with pytest.raises(OperationalException,
match=r"VolatilityFilter requires sort_direction to be either "
r"None .*'asc'.*'desc'"):
PairListManager(exchange_mock, whitelist_conf, MagicMock())
@pytest.mark.parametrize('pairlist,expected_pairlist', [
({"method": "VolatilityFilter", "sort_direction": "asc"},
['XRP/BTC', 'ETH/BTC', 'LTC/BTC', 'TKN/BTC']),
({"method": "VolatilityFilter", "sort_direction": "desc"},
['TKN/BTC', 'LTC/BTC', 'ETH/BTC', 'XRP/BTC']),
({"method": "VolatilityFilter", "sort_direction": "desc", 'min_volatility': 0.4},
['TKN/BTC', 'LTC/BTC', 'ETH/BTC']),
({"method": "VolatilityFilter", "sort_direction": "asc", 'min_volatility': 0.4},
['ETH/BTC', 'LTC/BTC', 'TKN/BTC']),
({"method": "VolatilityFilter", "sort_direction": "desc", 'max_volatility': 0.5},
['LTC/BTC', 'ETH/BTC', 'XRP/BTC']),
({"method": "VolatilityFilter", "sort_direction": "asc", 'max_volatility': 0.5},
['XRP/BTC', 'ETH/BTC', 'LTC/BTC']),
({"method": "RangeStabilityFilter", "sort_direction": "asc"},
['ETH/BTC', 'XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
({"method": "RangeStabilityFilter", "sort_direction": "desc"},
['TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'ETH/BTC']),
({"method": "RangeStabilityFilter", "sort_direction": "asc", 'min_rate_of_change': 0.4},
['XRP/BTC', 'LTC/BTC', 'TKN/BTC']),
({"method": "RangeStabilityFilter", "sort_direction": "desc", 'min_rate_of_change': 0.4},
['TKN/BTC', 'LTC/BTC', 'XRP/BTC']),
])
def test_VolatilityFilter_RangeStabilityFilter_sort(
mocker, whitelist_conf, tickers, time_machine, pairlist, expected_pairlist) -> None:
whitelist_conf['pairlists'] = [
{'method': 'VolumePairList', 'number_assets': 10},
pairlist
]
df1 = generate_test_data('1d', 10, '2022-01-05 00:00:00+00:00', random_seed=42)
df2 = generate_test_data('1d', 10, '2022-01-05 00:00:00+00:00', random_seed=2)
df3 = generate_test_data('1d', 10, '2022-01-05 00:00:00+00:00', random_seed=3)
df4 = generate_test_data('1d', 10, '2022-01-05 00:00:00+00:00', random_seed=4)
df5 = generate_test_data('1d', 10, '2022-01-05 00:00:00+00:00', random_seed=5)
df6 = generate_test_data('1d', 10, '2022-01-05 00:00:00+00:00', random_seed=6)
assert not df1.equals(df2)
time_machine.move_to('2022-01-15 00:00:00+00:00')
ohlcv_data = {
('ETH/BTC', '1d', CandleType.SPOT): df1,
('TKN/BTC', '1d', CandleType.SPOT): df2,
('LTC/BTC', '1d', CandleType.SPOT): df3,
('XRP/BTC', '1d', CandleType.SPOT): df4,
('HOT/BTC', '1d', CandleType.SPOT): df5,
('BLK/BTC', '1d', CandleType.SPOT): df6,
}
ohlcv_mock = MagicMock(return_value=ohlcv_data)
mocker.patch.multiple(
EXMS,
exchange_has=MagicMock(return_value=True),
refresh_latest_ohlcv=ohlcv_mock,
get_tickers=tickers
)
exchange = get_patched_exchange(mocker, whitelist_conf)
exchange.ohlcv_candle_limit = MagicMock(return_value=1000)
plm = PairListManager(exchange, whitelist_conf, MagicMock())
assert exchange.ohlcv_candle_limit.call_count == 2
plm.refresh_pairlist()
assert ohlcv_mock.call_count == 1
assert exchange.ohlcv_candle_limit.call_count == 2
assert plm.whitelist == expected_pairlist
plm.refresh_pairlist()
assert exchange.ohlcv_candle_limit.call_count == 2
assert ohlcv_mock.call_count == 1
def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None:
whitelist_conf['pairlists'] = [
{"method": "StaticPairList"},
@@ -1095,6 +1193,13 @@ def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers):
match='RangeStabilityFilter requires lookback_days to be >= 1'):
get_patched_freqtradebot(mocker, default_conf)
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
{'method': 'RangeStabilityFilter', 'sort_direction': 'something'}]
with pytest.raises(OperationalException,
match='RangeStabilityFilter requires sort_direction to be either None.*'):
get_patched_freqtradebot(mocker, default_conf)
@pytest.mark.parametrize('min_rate_of_change,max_rate_of_change,expected_length', [
(0.01, 0.99, 5),