refactor: move dataframe assertion to it's own class

This commit is contained in:
Matthias
2025-05-24 16:10:15 +02:00
parent 937bd892fd
commit b8e19ae78a
2 changed files with 45 additions and 27 deletions

View File

@@ -38,6 +38,7 @@ from freqtrade.strategy.informative_decorator import (
_create_and_merge_informative_pair,
_format_pair_name,
)
from freqtrade.strategy.strategy_validation import StrategyResultValidator
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util import dt_now
from freqtrade.wallets import Wallets
@@ -1213,13 +1214,13 @@ class IStrategy(ABC, HyperStrategyMixin):
return
try:
df_len, df_close, df_date = self.preserve_df(dataframe)
validator = StrategyResultValidator(dataframe, self.disable_dataframe_checks)
dataframe = strategy_safe_wrapper(self._analyze_ticker_internal, message="")(
dataframe, {"pair": pair}
)
self.assert_df(dataframe, df_len, df_close, df_date)
validator.assert_df(dataframe)
except StrategyError as error:
logger.warning(f"Unable to analyze candle (OHLCV) data for pair {pair}: {error}")
return
@@ -1236,31 +1237,6 @@ class IStrategy(ABC, HyperStrategyMixin):
for pair in pairs:
self.analyze_pair(pair)
@staticmethod
def preserve_df(dataframe: DataFrame) -> tuple[int, float, datetime]:
"""keep some data for dataframes"""
return len(dataframe), dataframe["close"].iloc[-1], dataframe["date"].iloc[-1]
def assert_df(self, dataframe: DataFrame, df_len: int, df_close: float, df_date: datetime):
"""
Ensure dataframe (length, last candle) was not modified, and has all elements we need.
"""
message_template = "Dataframe returned from strategy has mismatching {}."
message = ""
if dataframe is None:
message = "No dataframe returned (return statement missing?)."
elif df_len != len(dataframe):
message = message_template.format("length")
elif df_close != dataframe["close"].iloc[-1]:
message = message_template.format("last close price")
elif df_date != dataframe["date"].iloc[-1]:
message = message_template.format("last date")
if message:
if self.disable_dataframe_checks:
logger.warning(message)
else:
raise StrategyError(message)
def get_latest_candle(
self,
pair: str,

View File

@@ -0,0 +1,42 @@
import logging
from datetime import datetime
from pandas import DataFrame
from freqtrade.exceptions import StrategyError
logger = logging.getLogger(__name__)
class StrategyResultValidator:
def __init__(self, dataframe: DataFrame, warn_only: bool = False):
self._warn_only = warn_only
self._length: int = len(dataframe)
self._close: float = dataframe["close"].iloc[-1]
self._date: datetime = dataframe["date"].iloc[-1]
def assert_df(self, dataframe: DataFrame):
"""
Ensure dataframe (length, last candle) was not modified, and has all elements we need.
Raises a StrategyError if the dataframe does not match the expected values.
If warn_only is set, it will log a warning instead of raising an error.
:param dataframe: DataFrame to validate
:raises StrategyError: If the dataframe does not match the expected values.
:logs Warning: If warn_only is set and the dataframe does not match the expected values.
"""
message_template = "Dataframe returned from strategy has mismatching {}."
message = ""
if dataframe is None:
message = "No dataframe returned (return statement missing?)."
elif self._length != len(dataframe):
self.message = message_template.format("length")
elif self._close != dataframe["close"].iloc[-1]:
message = message_template.format("last close price")
elif self._date != dataframe["date"].iloc[-1]:
message = message_template.format("last date")
if message:
if self._warn_only:
logger.warning(message)
else:
raise StrategyError(message)