diff --git a/freqtrade/exceptions.py b/freqtrade/exceptions.py index 2f05ddb57..553a691ef 100644 --- a/freqtrade/exceptions.py +++ b/freqtrade/exceptions.py @@ -35,3 +35,10 @@ class TemporaryError(FreqtradeException): This could happen when an exchange is congested, unavailable, or the user has networking problems. Usually resolves itself after a time. """ + + +class StrategyError(FreqtradeException): + """ + Errors with custom user-code deteced. + Usually caused by errors in the strategy. + """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6e15c5183..c20bf0218 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -3,21 +3,22 @@ IStrategy interface This module defines the interface to apply for strategies """ import logging +import warnings from abc import ABC, abstractmethod from datetime import datetime, timezone from enum import Enum from typing import Dict, List, NamedTuple, Optional, Tuple -import warnings import arrow from pandas import DataFrame from freqtrade.data.dataprovider import DataProvider +from freqtrade.exceptions import StrategyError from freqtrade.exchange import timeframe_to_minutes from freqtrade.persistence import Trade +from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets - logger = logging.getLogger(__name__) @@ -255,20 +256,12 @@ class IStrategy(ABC): return False, False try: - dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair}) - except ValueError as error: - logger.warning( - 'Unable to analyze ticker for pair %s: %s', - pair, - str(error) - ) - return False, False - except Exception as error: - logger.exception( - 'Unexpected error when analyzing ticker for pair %s: %s', - pair, - str(error) - ) + dataframe = strategy_safe_wrapper( + self._analyze_ticker_internal, message="" + )(dataframe, {'pair': pair}) + except StrategyError as error: + logger.warning(f"Unable to analyze ticker for pair {pair}: {error}") + return False, False if dataframe.empty: diff --git a/freqtrade/strategy/strategy_wrapper.py b/freqtrade/strategy/strategy_wrapper.py new file mode 100644 index 000000000..61c986732 --- /dev/null +++ b/freqtrade/strategy/strategy_wrapper.py @@ -0,0 +1,29 @@ +import logging + +from freqtrade.exceptions import StrategyError + +logger = logging.getLogger(__name__) + + +def strategy_safe_wrapper(f, message: str, default_retval=None): + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except ValueError as error: + logger.warning( + f"{message}" + f"Strategy caused the following exception: {error}" + f"{f}" + ) + if not default_retval: + raise StrategyError(str(error)) from error + return default_retval + except Exception as error: + logger.exception( + f"Unexpected error {error} calling {f}" + ) + if not default_retval: + raise StrategyError(str(error)) from error + return default_retval + + return wrapper diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 89c38bda1..2959fe62c 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -10,8 +10,8 @@ from freqtrade.configuration import TimeRange from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.history import load_tickerdata_file from freqtrade.persistence import Trade -from tests.conftest import get_patched_exchange, log_has from freqtrade.strategy.default_strategy import DefaultStrategy +from tests.conftest import get_patched_exchange, log_has, log_has_re # Avoid to reinit the same object again and again _STRATEGY = DefaultStrategy(config={}) @@ -65,7 +65,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_hi ) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], ticker_history) - assert log_has('Unable to analyze ticker for pair foo: xyz', caplog) + assert log_has_re(r'Strategy caused the following exception: xyz.*', caplog) def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history):