diff --git a/tests/conftest.py b/tests/conftest.py index f87fa59c5..9f71709f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -104,6 +104,13 @@ 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'): + """ Generates data in the ohlcv format used by ccxt """ + df = generate_test_data(timeframe, size, start) + df['date'] = df.loc[:, 'date'].view(np.int64) // 1000 // 1000 + return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns))) + + # Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines # TODO: This should be replaced with AsyncMock once support for python 3.7 is dropped. def get_mock_coro(return_value=None, side_effect=None): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index bf9c1ae72..bcd1473c6 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -22,7 +22,8 @@ from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_CO calculate_backoff, remove_credentials) from freqtrade.exchange.exchange import amount_to_contract_precision from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re +from tests.conftest import (generate_test_data_raw, get_mock_coro, get_patched_exchange, log_has, + log_has_re, num_log_has_re) # Make sure to always keep one exchange here which is NOT subclassed!! @@ -2182,6 +2183,79 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None assert len(res) == 1 +@pytest.mark.parametrize('candle_type', [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT]) +def test_refresh_latest_ohlcv_cache(mocker, default_conf, candle_type, time_machine) -> None: + start = datetime(2021, 8, 1, 0, 0, 0, 0, tzinfo=timezone.utc) + ohlcv = generate_test_data_raw('1h', 100, start.strftime('%Y-%m-%d')) + time_machine.move_to(start + timedelta(hours=99, minutes=30)) + + exchange = get_patched_exchange(mocker, default_conf) + exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) + pair1 = ('IOTA/ETH', '1h', candle_type) + pair2 = ('XRP/ETH', '1h', candle_type) + pairs = [pair1, pair2] + + # No caching + assert not exchange._klines + res = exchange.refresh_latest_ohlcv(pairs, cache=False) + assert exchange._api_async.fetch_ohlcv.call_count == 2 + assert len(res) == 2 + assert len(res[pair1]) == 99 + assert len(res[pair2]) == 99 + assert not exchange._klines + exchange._api_async.fetch_ohlcv.reset_mock() + + # With caching + res = exchange.refresh_latest_ohlcv(pairs) + assert exchange._api_async.fetch_ohlcv.call_count == 2 + assert len(res) == 2 + assert len(res[pair1]) == 99 + assert len(res[pair2]) == 99 + assert exchange._klines + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + exchange._api_async.fetch_ohlcv.reset_mock() + + # Returned from cache + res = exchange.refresh_latest_ohlcv(pairs) + assert exchange._api_async.fetch_ohlcv.call_count == 0 + assert len(res) == 2 + assert len(res[pair1]) == 99 + assert len(res[pair2]) == 99 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + + # Move time 1 candle further but result didn't change yet + time_machine.move_to(start + timedelta(hours=101)) + res = exchange.refresh_latest_ohlcv(pairs) + assert exchange._api_async.fetch_ohlcv.call_count == 2 + assert len(res) == 2 + assert len(res[pair1]) == 99 + assert len(res[pair2]) == 99 + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + refresh_pior = exchange._pairs_last_refresh_time[pair1] + + # New candle on exchange - only return 50 candles (but one candle further) + new_startdate = (start + timedelta(hours=51)).strftime('%Y-%m-%d %H:%M') + ohlcv = generate_test_data_raw('1h', 50, new_startdate) + exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv) + res = exchange.refresh_latest_ohlcv(pairs) + assert exchange._api_async.fetch_ohlcv.call_count == 2 + assert len(res) == 2 + assert len(res[pair1]) == 100 + assert len(res[pair2]) == 100 + assert refresh_pior != exchange._pairs_last_refresh_time[pair1] + + assert exchange._pairs_last_refresh_time[pair1] == ohlcv[-1][0] // 1000 + assert exchange._pairs_last_refresh_time[pair2] == ohlcv[-1][0] // 1000 + exchange._api_async.fetch_ohlcv.reset_mock() + + # Retry same call - no action. + res = exchange.refresh_latest_ohlcv(pairs) + assert exchange._api_async.fetch_ohlcv.call_count == 0 + assert len(res) == 2 + assert len(res[pair1]) == 100 + assert len(res[pair2]) == 100 + + @pytest.mark.asyncio @pytest.mark.parametrize("exchange_name", EXCHANGES) async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_name):