diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index c791be615..7396f2a89 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -518,10 +518,10 @@ class SampleStrategy(IStrategy): # Use the helper function merge_informative_pair to safely merge the pair # Automatically renames the columns and merges a shorter timeframe dataframe and a longer timeframe informative pair - # FFill to have the 1d value available in every row throughout the day. - # Without this, comparisons would only work once per day. + # use ffill to have the 1d value available in every row throughout the day. + # Without this, comparisons between columns of the original and the informative pair would only work once per day. # Full documentation of this method, see below - dataframe = merge_informative_pair(dataframe, informative_pairs, inf_tf, ffill=True) + dataframe = merge_informative_pair(dataframe, informative_pairs, self.timeframe, inf_tf, ffill=True) # Calculate rsi of the original dataframe (5m timeframe) dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) @@ -589,6 +589,7 @@ All columns of the informative dataframe will be available on the returning data # This is necessary since the data is always the "open date" # and a 15m candle starting at 12:15 should not know the close of the 1h candle from 12:00 to 13:00 minutes = timeframe_to_minutes(inf_tf) + # Only do this if the timeframes are different: informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes, 'm') # Combine the 2 dataframes diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 2684e7b03..0fa7f4258 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -3,7 +3,7 @@ from freqtrade.exchange import timeframe_to_minutes def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, - timeframe_inf: str, ffill: bool = True) -> pd.DataFrame: + timeframe: str, timeframe_inf: str, ffill: bool = True) -> pd.DataFrame: """ Correctly merge informative samples to the original dataframe, avoiding lookahead bias. @@ -20,13 +20,18 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, :param dataframe: Original dataframe :param informative: Informative pair, most likely loaded via dp.get_pair_dataframe + :param timeframe: Timeframe of the original pair sample. :param timeframe_inf: Timeframe of the informative pair sample. :param ffill: Forwardfill missing values - optional but usually required """ # Rename columns to be unique - minutes = timeframe_to_minutes(timeframe_inf) - informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes, 'm') + minutes_inf = timeframe_to_minutes(timeframe_inf) + if timeframe == timeframe_inf: + # No need to forwardshift if the timeframes are identical + informative['date_merge'] = informative["date"] + else: + informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes_inf, 'm') informative.columns = [f"{col}_{timeframe_inf}" for col in informative.columns] diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 9201d91e1..4b29bf304 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -28,7 +28,7 @@ def test_merge_informative_pair(): data = generate_test_data('15m', 40) informative = generate_test_data('1h', 40) - result = merge_informative_pair(data, informative, '1h', ffill=True) + result = merge_informative_pair(data, informative, '15m', '1h', ffill=True) assert isinstance(result, pd.DataFrame) assert len(result) == len(data) assert 'date' in result.columns @@ -59,3 +59,30 @@ def test_merge_informative_pair(): assert result.iloc[7]['date_1h'] == result.iloc[0]['date'] # Next 4 rows contain the next Hourly date original date row 4 assert result.iloc[8]['date_1h'] == result.iloc[4]['date'] + + +def test_merge_informative_pair_same(): + data = generate_test_data('15m', 40) + informative = generate_test_data('15m', 40) + + result = merge_informative_pair(data, informative, '15m', '15m', ffill=True) + assert isinstance(result, pd.DataFrame) + assert len(result) == len(data) + assert 'date' in result.columns + assert result['date'].equals(data['date']) + assert 'date_15m' in result.columns + + assert 'open' in result.columns + assert 'open_15m' in result.columns + assert result['open'].equals(data['open']) + + assert 'close' in result.columns + assert 'close_15m' in result.columns + assert result['close'].equals(data['close']) + + assert 'volume' in result.columns + assert 'volume_15m' in result.columns + assert result['volume'].equals(data['volume']) + + # Dates match 1:1 + assert result['date_15m'].equals(result['date'])