diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 4b16550c5..8c6c1e111 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -301,6 +301,7 @@ def get_signal(pair: str, signal: SignalType) -> bool: return False if dataframe.empty: + logger.warning('Empty dataframe for pair %s', pair) return False latest = dataframe.iloc[-1] @@ -308,8 +309,12 @@ def get_signal(pair: str, signal: SignalType) -> bool: # Check if dataframe is out of date signal_date = arrow.get(latest['date']) if signal_date < arrow.now() - timedelta(minutes=10): + logger.warning('Too old dataframe for pair %s', pair) return False + # FIX: 20180109, there could be some confusion because we will make a + # boolean result (execute the action or not depending on the signal). + # But the above checks can also return False, and we hide that. result = latest[signal.value] == 1 logger.debug('%s_trigger: %s (pair=%s, signal=%s)', signal.value, latest['date'], pair, result) return result diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index 040c45f26..67a5a54f5 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -1,8 +1,10 @@ # pragma pylint: disable=missing-docstring,W0621 import json +from functools import reduce from unittest.mock import MagicMock import arrow +import datetime import pytest from pandas import DataFrame @@ -10,6 +12,14 @@ from freqtrade.analyze import parse_ticker_dataframe, populate_buy_trend, popula get_signal, SignalType, populate_sell_trend +def log_has(line, logs): + # caplog mocker returns log as a tuple: ('freqtrade.analyze', logging.WARNING, 'foobar') + # and we want to match line against foobar in the tuple + return reduce(lambda a, b: a or b, + filter(lambda x: x[2] == line, logs), + False) + + @pytest.fixture def result(): with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file: @@ -65,6 +75,43 @@ def test_returns_latest_sell_signal(mocker): assert not get_signal('BTC-ETH', SignalType.SELL) +def test_get_signal_empty(mocker, caplog): + mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None) + assert not get_signal('foo', SignalType.BUY) + assert log_has('Empty ticker history for pair foo', + caplog.record_tuples) + + +def test_get_signal_execption_valueerror(mocker, caplog): + mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) + mocker.patch('freqtrade.analyze.analyze_ticker', + side_effect=ValueError('xyz')) + assert not get_signal('foo', SignalType.BUY) + assert log_has('Unable to analyze ticker for pair foo: xyz', + caplog.record_tuples) + + +# This error should never occur becase analyze_ticker is run first, +# and that function can only add columns, it cant delete all rows from the dataframe +def test_get_signal_empty_dataframe(mocker, caplog): + mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) + mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([])) + assert not get_signal('xyz', SignalType.BUY) + assert log_has('Empty dataframe for pair xyz', + caplog.record_tuples) + + +def test_get_signal_old_dataframe(mocker, caplog): + mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) + # FIX: The get_signal function has hardcoded 10, which we must inturn hardcode + oldtime = arrow.utcnow() - datetime.timedelta(minutes=11) + ticks = DataFrame([{'buy': 1, 'date': oldtime}]) + mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks)) + assert not get_signal('xyz', SignalType.BUY) + assert log_has('Too old dataframe for pair xyz', + caplog.record_tuples) + + def test_get_signal_handles_exceptions(mocker): mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch('freqtrade.analyze.analyze_ticker',