From f54fecaebaaa009dd4a5da2c83dcb85516be8a6a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Sep 2020 19:58:26 +0200 Subject: [PATCH 01/22] Expose helpermethods thorugh freqtrade.strategy --- freqtrade/strategy/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index 40a4a0bea..91ea0e075 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -1 +1,4 @@ -from freqtrade.strategy.interface import IStrategy # noqa: F401 +# flake8: noqa: F401 +from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_prev_date, + timeframe_to_seconds, timeframe_to_next_date, timeframe_to_msecs) +from freqtrade.strategy.interface import IStrategy From e268bd192e85868cb4da9e74dceb652d249ffbe6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Sep 2020 19:59:04 +0200 Subject: [PATCH 02/22] Fix informative sample documentation --- docs/strategy-customization.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index be08faa2d..4362c251f 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -483,6 +483,10 @@ if self.dp: ### Complete Data-provider sample ```python +from freqtrade.strategy import IStrategy, timeframe_to_minutes +from pandas import DataFrame +import pandas as pd + class SampleStrategy(IStrategy): # strategy init stuff... @@ -518,9 +522,15 @@ class SampleStrategy(IStrategy): # Assuming inf_tf = '1d' - then the columns will now be: # date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d + # Shift date by 1 Frequency unit + # 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) + informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes, 'm') + # Combine the 2 dataframes # all indicators on the informative sample MUST be calculated before this point - dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_{inf_tf}', how='left') + dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_merge_{inf_tf}', how='left') # FFill to have the 1d value available in every row throughout the day. # Without this, comparisons would only work once per day. dataframe = dataframe.ffill() From 79ea8cf7719879e942d4c49dddd63cafd2d38cfe Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Sep 2020 20:02:41 +0200 Subject: [PATCH 03/22] Improve wording --- docs/strategy-customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 4362c251f..e2548e510 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -522,7 +522,7 @@ class SampleStrategy(IStrategy): # Assuming inf_tf = '1d' - then the columns will now be: # date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d - # Shift date by 1 Frequency unit + # Shift date by 1 candle # 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) From bd4f3d838ab959c3c8ff9f0e39daaeb62c3bddce Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Sep 2020 19:44:35 +0200 Subject: [PATCH 04/22] Implement merge_informative_pairs helper --- freqtrade/strategy/__init__.py | 1 + freqtrade/strategy/strategy_helper.py | 39 ++++++++++++++++ tests/strategy/test_strategy_helpers.py | 61 +++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 freqtrade/strategy/strategy_helper.py create mode 100644 tests/strategy/test_strategy_helpers.py diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index 91ea0e075..5758bbbcc 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -2,3 +2,4 @@ from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_prev_date, timeframe_to_seconds, timeframe_to_next_date, timeframe_to_msecs) from freqtrade.strategy.interface import IStrategy +from freqtrade.strategy.strategy_helper import merge_informative_pairs diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py new file mode 100644 index 000000000..ce98cccba --- /dev/null +++ b/freqtrade/strategy/strategy_helper.py @@ -0,0 +1,39 @@ +import pandas as pd +from freqtrade.exchange import timeframe_to_minutes + + +def merge_informative_pairs(dataframe: pd.DataFrame, informative: pd.DataFrame, + timeframe_inf: str, ffill: bool = True) -> pd.DataFrame: + """ + Correctly merge informative samples to the original dataframe, avoiding lookahead bias. + + Since dates are candle open dates, merging a 15m candle that starts at 15:00, and a + 1h candle that starts at 15:00 will result in all candles to know the close at 16:00 + which they should not know. + + Moves the date of the informative pair by 1 time interval forward. + This way, the 14:00 1h candle is merged to 15:00 15m candle, since the 14:00 1h candle is the + last candle that's closed at 15:00, 15:15, 15:30 or 15:45. + + :param dataframe: Original dataframe + :param informative: Informative pair, most likely loaded via dp.get_pair_dataframe + :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') + + informative.columns = [f"{col}_{timeframe_inf}" for col in informative.columns] + + # Combine the 2 dataframes + # all indicators on the informative sample MUST be calculated before this point + dataframe = pd.merge(dataframe, informative, left_on='date', + right_on=f'date_merge_{timeframe_inf}', how='left') + dataframe = dataframe.drop(f'date_merge_{timeframe_inf}', axis=1) + + if ffill: + dataframe = dataframe.ffill() + + return dataframe diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py new file mode 100644 index 000000000..89bbba2c1 --- /dev/null +++ b/tests/strategy/test_strategy_helpers.py @@ -0,0 +1,61 @@ +import pandas as pd +import numpy as np + +from freqtrade.strategy import merge_informative_pairs, timeframe_to_minutes + + +def generate_test_data(timeframe: str, size: int): + np.random.seed(42) + tf_mins = timeframe_to_minutes(timeframe) + + base = np.random.normal(20, 2, size=size) + + date = pd.period_range('2020-07-05', periods=size, freq=f'{tf_mins}min').to_timestamp() + df = pd.DataFrame({ + 'date': date, + 'open': base, + 'high': base + np.random.normal(2, 1, size=size), + 'low': base - np.random.normal(2, 1, size=size), + 'close': base + np.random.normal(0, 1, size=size), + 'volume': np.random.normal(200, size=size) + } + ) + df = df.dropna() + return df + + +def test_merge_informative_pairs(): + data = generate_test_data('15m', 40) + informative = generate_test_data('1h', 40) + + result = merge_informative_pairs(data, informative, '1h', 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_1h' in result.columns + + assert 'open' in result.columns + assert 'open_1h' in result.columns + assert result['open'].equals(data['open']) + + assert 'close' in result.columns + assert 'close_1h' in result.columns + assert result['close'].equals(data['close']) + + assert 'volume' in result.columns + assert 'volume_1h' in result.columns + assert result['volume'].equals(data['volume']) + + # First 4 rows are empty + assert result.iloc[0]['date_1h'] is pd.NaT + assert result.iloc[1]['date_1h'] is pd.NaT + assert result.iloc[2]['date_1h'] is pd.NaT + assert result.iloc[3]['date_1h'] is pd.NaT + # Next 4 rows contain the starting date (0:00) + assert result.iloc[4]['date_1h'] == result.iloc[0]['date'] + assert result.iloc[5]['date_1h'] == result.iloc[0]['date'] + assert result.iloc[6]['date_1h'] == result.iloc[0]['date'] + 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'] From 7bc89279148872e43d4f6f6233ffbc864bc1f436 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Sep 2020 20:02:31 +0200 Subject: [PATCH 05/22] Add documentation for merge_informative_pair helper --- docs/strategy-customization.md | 79 +++++++++++++++++++------ freqtrade/strategy/__init__.py | 2 +- freqtrade/strategy/strategy_helper.py | 7 ++- tests/strategy/test_strategy_helpers.py | 6 +- 4 files changed, 71 insertions(+), 23 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index e2548e510..c791be615 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -483,9 +483,8 @@ if self.dp: ### Complete Data-provider sample ```python -from freqtrade.strategy import IStrategy, timeframe_to_minutes +from freqtrade.strategy import IStrategy, merge_informative_pairs from pandas import DataFrame -import pandas as pd class SampleStrategy(IStrategy): # strategy init stuff... @@ -517,23 +516,12 @@ class SampleStrategy(IStrategy): # Get the 14 day rsi informative['rsi'] = ta.RSI(informative, timeperiod=14) - # Rename columns to be unique - informative.columns = [f"{col}_{inf_tf}" for col in informative.columns] - # Assuming inf_tf = '1d' - then the columns will now be: - # date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d - - # Shift date by 1 candle - # 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) - informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes, 'm') - - # Combine the 2 dataframes - # all indicators on the informative sample MUST be calculated before this point - dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_merge_{inf_tf}', how='left') + # 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. - dataframe = dataframe.ffill() + # Full documentation of this method, see below + dataframe = merge_informative_pair(dataframe, informative_pairs, inf_tf, ffill=True) # Calculate rsi of the original dataframe (5m timeframe) dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) @@ -557,6 +545,63 @@ class SampleStrategy(IStrategy): *** +## Helper functions + +### *merge_informative_pair()* + +This method helps you merge an informative pair to a regular dataframe without lookahead bias. +It's there to help you merge the dataframe in a safe and consistent way. + +Options: + +- Rename the columns for you to create unique columns +- Merge the dataframe without lookahead bias +- Forward-fill (optional) + +All columns of the informative dataframe will be available on the returning dataframe in a renamed fashion: + +!!! Example "Column renaming" + Assuming `inf_tf = '1d'` the resulting columns will be: + + ``` python + 'date', 'open', 'high', 'low', 'close', 'rsi' # from the original dataframe + 'date_1d', 'open_1d', 'high_1d', 'low_1d', 'close_1d', 'rsi_1d' # from the informative dataframe + ``` + +??? Example "Column renaming - 1h" + Assuming `inf_tf = '1h'` the resulting columns will be: + + ``` python + 'date', 'open', 'high', 'low', 'close', 'rsi' # from the original dataframe + 'date_1h', 'open_1h', 'high_1h', 'low_1h', 'close_1h', 'rsi_1h' # from the informative dataframe + ``` + +??? Example "Custom implementation" + A custom implementation for this is possible, and can be done as follows: + + ``` python + # Rename columns to be unique + informative.columns = [f"{col}_{inf_tf}" for col in informative.columns] + # Assuming inf_tf = '1d' - then the columns will now be: + # date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d + + # Shift date by 1 candle + # 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) + informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes, 'm') + + # Combine the 2 dataframes + # all indicators on the informative sample MUST be calculated before this point + dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_merge_{inf_tf}', how='left') + # FFill to have the 1d value available in every row throughout the day. + # Without this, comparisons would only work once per day. + dataframe = dataframe.ffill() + + ``` + +*** + ## Additional data (Wallets) The strategy provides access to the `Wallets` object. This contains the current balances on the exchange. diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py index 5758bbbcc..d1510489e 100644 --- a/freqtrade/strategy/__init__.py +++ b/freqtrade/strategy/__init__.py @@ -2,4 +2,4 @@ from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_prev_date, timeframe_to_seconds, timeframe_to_next_date, timeframe_to_msecs) from freqtrade.strategy.interface import IStrategy -from freqtrade.strategy.strategy_helper import merge_informative_pairs +from freqtrade.strategy.strategy_helper import merge_informative_pair diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index ce98cccba..2684e7b03 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -2,8 +2,8 @@ import pandas as pd from freqtrade.exchange import timeframe_to_minutes -def merge_informative_pairs(dataframe: pd.DataFrame, informative: pd.DataFrame, - timeframe_inf: str, ffill: bool = True) -> pd.DataFrame: +def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, + timeframe_inf: str, ffill: bool = True) -> pd.DataFrame: """ Correctly merge informative samples to the original dataframe, avoiding lookahead bias. @@ -15,6 +15,9 @@ def merge_informative_pairs(dataframe: pd.DataFrame, informative: pd.DataFrame, This way, the 14:00 1h candle is merged to 15:00 15m candle, since the 14:00 1h candle is the last candle that's closed at 15:00, 15:15, 15:30 or 15:45. + Assuming inf_tf = '1d' - then the resulting columns will be: + date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d + :param dataframe: Original dataframe :param informative: Informative pair, most likely loaded via dp.get_pair_dataframe :param timeframe_inf: Timeframe of the informative pair sample. diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 89bbba2c1..9201d91e1 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -1,7 +1,7 @@ import pandas as pd import numpy as np -from freqtrade.strategy import merge_informative_pairs, timeframe_to_minutes +from freqtrade.strategy import merge_informative_pair, timeframe_to_minutes def generate_test_data(timeframe: str, size: int): @@ -24,11 +24,11 @@ def generate_test_data(timeframe: str, size: int): return df -def test_merge_informative_pairs(): +def test_merge_informative_pair(): data = generate_test_data('15m', 40) informative = generate_test_data('1h', 40) - result = merge_informative_pairs(data, informative, '1h', ffill=True) + result = merge_informative_pair(data, informative, '1h', ffill=True) assert isinstance(result, pd.DataFrame) assert len(result) == len(data) assert 'date' in result.columns From cc684c51415afd7e1b682242ecd8c76f594e8c1d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Sep 2020 20:09:02 +0200 Subject: [PATCH 06/22] Correctly handle identical timerame merges --- docs/strategy-customization.md | 7 +++--- freqtrade/strategy/strategy_helper.py | 11 +++++++--- tests/strategy/test_strategy_helpers.py | 29 ++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 7 deletions(-) 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']) From 71af64af94ba07a19181618e58f0dbfa8480709f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 4 Sep 2020 20:10:43 +0200 Subject: [PATCH 07/22] Move comment to the right place --- freqtrade/strategy/strategy_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 0fa7f4258..1fbf618bd 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -24,7 +24,6 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, :param timeframe_inf: Timeframe of the informative pair sample. :param ffill: Forwardfill missing values - optional but usually required """ - # Rename columns to be unique minutes_inf = timeframe_to_minutes(timeframe_inf) if timeframe == timeframe_inf: @@ -33,6 +32,7 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, else: informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes_inf, 'm') + # Rename columns to be unique informative.columns = [f"{col}_{timeframe_inf}" for col in informative.columns] # Combine the 2 dataframes From 7852feab0566ecdda19e705cd88c7c7bd61cd52b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 7 Sep 2020 09:06:43 +0200 Subject: [PATCH 08/22] support smaller timeframes --- docs/strategy-customization.md | 6 +++++- freqtrade/strategy/strategy_helper.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 7396f2a89..a1de1044c 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -521,7 +521,7 @@ class SampleStrategy(IStrategy): # 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, self.timeframe, inf_tf, ffill=True) + dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True) # Calculate rsi of the original dataframe (5m timeframe) dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) @@ -601,6 +601,10 @@ All columns of the informative dataframe will be available on the returning data ``` +!!! Warning "Informative timeframe < timeframe" + Using informative timeframes smaller than the dataframe timeframe is not recommended with this method, as it will not use any of the additional information this would provide. + To use the more detailed information properly, more advanced methods should be applied (which are out of scope for freqtrade documentation, as it'll depend on the respective need). + *** ## Additional data (Wallets) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 1fbf618bd..1a5b2d0f8 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -26,7 +26,8 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, """ minutes_inf = timeframe_to_minutes(timeframe_inf) - if timeframe == timeframe_inf: + minutes = timeframe_to_minutes(timeframe) + if minutes >= minutes_inf: # No need to forwardshift if the timeframes are identical informative['date_merge'] = informative["date"] else: From 014fcb36f4e6b3da535dcc7ebaa604ea47f9e2d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2020 07:09:07 +0000 Subject: [PATCH 09/22] Bump mkdocs-material from 5.5.11 to 5.5.12 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 5.5.11 to 5.5.12. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/5.5.11...5.5.12) Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index c8f08d12a..6408616a0 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,2 +1,2 @@ -mkdocs-material==5.5.11 +mkdocs-material==5.5.12 mdx_truly_sane_lists==1.2 From 534404c284a3de1b8bc9b6cb4f8e9603c32f81c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2020 07:09:22 +0000 Subject: [PATCH 10/22] Bump ccxt from 1.33.72 to 1.34.3 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.33.72 to 1.34.3. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.33.72...1.34.3) Signed-off-by: dependabot[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index f305d8793..58912bc0c 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.33.72 +ccxt==1.34.3 SQLAlchemy==1.3.19 python-telegram-bot==12.8 arrow==0.16.0 From ff0e73a9e592e4c61eca8fce97b0c84d5b886844 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2020 07:09:23 +0000 Subject: [PATCH 11/22] Bump scikit-optimize from 0.7.4 to 0.8.1 Bumps [scikit-optimize](https://github.com/scikit-optimize/scikit-optimize) from 0.7.4 to 0.8.1. - [Release notes](https://github.com/scikit-optimize/scikit-optimize/releases) - [Changelog](https://github.com/scikit-optimize/scikit-optimize/blob/master/CHANGELOG.md) - [Commits](https://github.com/scikit-optimize/scikit-optimize/compare/v0.7.4...v0.8.1) Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index fbc679eaa..ea24196b9 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -4,7 +4,7 @@ # Required for hyperopt scipy==1.5.2 scikit-learn==0.23.1 -scikit-optimize==0.7.4 +scikit-optimize==0.8.1 filelock==3.0.12 joblib==0.16.0 progressbar2==3.52.1 From f20318fad1b27039c8269984f4ea78cecb1aab90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2020 07:37:12 +0000 Subject: [PATCH 12/22] Bump scikit-learn from 0.23.1 to 0.23.2 Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 0.23.1 to 0.23.2. - [Release notes](https://github.com/scikit-learn/scikit-learn/releases) - [Commits](https://github.com/scikit-learn/scikit-learn/compare/0.23.1...0.23.2) Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index ea24196b9..4d884b4fe 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -3,7 +3,7 @@ # Required for hyperopt scipy==1.5.2 -scikit-learn==0.23.1 +scikit-learn==0.23.2 scikit-optimize==0.8.1 filelock==3.0.12 joblib==0.16.0 From 4cf66e2fba76d3aab734bffe94063ca906c74bbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2020 13:29:22 +0000 Subject: [PATCH 13/22] Bump progressbar2 from 3.52.1 to 3.53.1 Bumps [progressbar2](https://github.com/WoLpH/python-progressbar) from 3.52.1 to 3.53.1. - [Release notes](https://github.com/WoLpH/python-progressbar/releases) - [Changelog](https://github.com/WoLpH/python-progressbar/blob/develop/CHANGES.rst) - [Commits](https://github.com/WoLpH/python-progressbar/compare/v3.52.1...v3.53.1) Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 4d884b4fe..b47331aa3 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -7,4 +7,4 @@ scikit-learn==0.23.2 scikit-optimize==0.8.1 filelock==3.0.12 joblib==0.16.0 -progressbar2==3.52.1 +progressbar2==3.53.1 From 986e767d6c4ec460cc35b95db178e19bbef9de2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2020 13:29:27 +0000 Subject: [PATCH 14/22] Bump blosc from 1.9.1 to 1.9.2 Bumps [blosc](https://github.com/blosc/python-blosc) from 1.9.1 to 1.9.2. - [Release notes](https://github.com/blosc/python-blosc/releases) - [Changelog](https://github.com/Blosc/python-blosc/blob/master/RELEASE_NOTES.rst) - [Commits](https://github.com/blosc/python-blosc/compare/v1.9.1...v1.9.2) Signed-off-by: dependabot[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 58912bc0c..5efc6a2ee 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -14,7 +14,7 @@ tabulate==0.8.7 pycoingecko==1.3.0 jinja2==2.11.2 tables==3.6.1 -blosc==1.9.1 +blosc==1.9.2 # find first, C search in arrays py_find_1st==1.1.4 From d8dae46544987a2f5d672d1128851d47d5f6d9bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2020 13:29:31 +0000 Subject: [PATCH 15/22] Bump ccxt from 1.34.3 to 1.34.11 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.34.3 to 1.34.11. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst) - [Commits](https://github.com/ccxt/ccxt/compare/1.34.3...1.34.11) Signed-off-by: dependabot[bot] --- requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-common.txt b/requirements-common.txt index 58912bc0c..faa022c4c 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,6 +1,6 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.34.3 +ccxt==1.34.11 SQLAlchemy==1.3.19 python-telegram-bot==12.8 arrow==0.16.0 From 8c97b83b8c44de9f7b921cba6bec3bdeb39e5527 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2020 13:29:36 +0000 Subject: [PATCH 16/22] Bump pandas from 1.1.1 to 1.1.2 Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.1.1 to 1.1.2. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md) - [Commits](https://github.com/pandas-dev/pandas/compare/v1.1.1...v1.1.2) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 66f4cbc5f..a1a1fb250 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ -r requirements-common.txt numpy==1.19.1 -pandas==1.1.1 +pandas==1.1.2 From 4480b3b39374439296407128b816a797749865c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 9 Sep 2020 15:39:35 +0200 Subject: [PATCH 17/22] Fix error in documentation (wrong sequence of steps) --- docs/strategy-customization.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index a1de1044c..615be0247 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -580,10 +580,6 @@ All columns of the informative dataframe will be available on the returning data A custom implementation for this is possible, and can be done as follows: ``` python - # Rename columns to be unique - informative.columns = [f"{col}_{inf_tf}" for col in informative.columns] - # Assuming inf_tf = '1d' - then the columns will now be: - # date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d # Shift date by 1 candle # This is necessary since the data is always the "open date" @@ -592,6 +588,11 @@ All columns of the informative dataframe will be available on the returning data # Only do this if the timeframes are different: informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes, 'm') + # Rename columns to be unique + informative.columns = [f"{col}_{inf_tf}" for col in informative.columns] + # Assuming inf_tf = '1d' - then the columns will now be: + # date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d + # Combine the 2 dataframes # all indicators on the informative sample MUST be calculated before this point dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_merge_{inf_tf}', how='left') From 6a08fee25b88b8f4824bcc5f76f2dc3ee7c96529 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 10 Sep 2020 08:04:04 +0200 Subject: [PATCH 18/22] Fix wrong import in documentation --- docs/strategy-customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 615be0247..14d5fcd84 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -483,7 +483,7 @@ if self.dp: ### Complete Data-provider sample ```python -from freqtrade.strategy import IStrategy, merge_informative_pairs +from freqtrade.strategy import IStrategy, merge_informative_pair from pandas import DataFrame class SampleStrategy(IStrategy): From b8773de5b0d39163c9ded61e312578791ad9bb98 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Sep 2020 06:44:20 +0200 Subject: [PATCH 19/22] scoped sessions should be closed after requests --- freqtrade/rpc/api_server.py | 10 +++++++++- tests/rpc/test_rpc_apiserver.py | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server.py b/freqtrade/rpc/api_server.py index 4bbc8a1dc..0ae0698cd 100644 --- a/freqtrade/rpc/api_server.py +++ b/freqtrade/rpc/api_server.py @@ -17,8 +17,9 @@ from werkzeug.serving import make_server from freqtrade.__init__ import __version__ from freqtrade.constants import DATETIME_PRINT_FORMAT -from freqtrade.rpc.rpc import RPC, RPCException +from freqtrade.persistence import Trade from freqtrade.rpc.fiat_convert import CryptoToFiatConverter +from freqtrade.rpc.rpc import RPC, RPCException logger = logging.getLogger(__name__) @@ -70,6 +71,11 @@ def rpc_catch_errors(func: Callable[..., Any]): return func_wrapper +def shutdown_session(exception=None): + # Remove scoped session + Trade.session.remove() + + class ApiServer(RPC): """ This class runs api server and provides rpc.rpc functionality to it @@ -104,6 +110,8 @@ class ApiServer(RPC): self.jwt = JWTManager(self.app) self.app.json_encoder = ArrowJSONEncoder + self.app.teardown_appcontext(shutdown_session) + # Register application handling self.register_rest_rpc_urls() diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index d9f5bf781..f8256f1ba 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -471,6 +471,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): assert rc.json == {"error": "Error querying _edge: Edge is not enabled."} +@pytest.mark.usefixtures("init_persistence") def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, limit_sell_order): ftbot, client = botclient patch_get_signal(ftbot, (True, False)) @@ -498,6 +499,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li assert rc.json['best_pair'] == '' assert rc.json['best_rate'] == 0 + trade = Trade.query.first() trade.update(limit_sell_order) trade.close_date = datetime.utcnow() From 0c9301e74a313a40e3829aed1ab701e04858d262 Mon Sep 17 00:00:00 2001 From: caudurodev Date: Fri, 11 Sep 2020 08:41:33 +0200 Subject: [PATCH 20/22] FIX: added missing ) for SQLite insert --- docs/sql_cheatsheet.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index cf785ced6..249e935ef 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -47,6 +47,7 @@ sqlite3 ```sql CREATE TABLE trades +( id INTEGER NOT NULL, exchange VARCHAR NOT NULL, pair VARCHAR NOT NULL, From 90d97c536d5b0a8ad7d863304023e418fe6f766e Mon Sep 17 00:00:00 2001 From: caudurodev Date: Fri, 11 Sep 2020 08:42:42 +0200 Subject: [PATCH 21/22] FIX: added missing ) for SQLite insert --- docs/sql_cheatsheet.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/sql_cheatsheet.md b/docs/sql_cheatsheet.md index 249e935ef..168d416ab 100644 --- a/docs/sql_cheatsheet.md +++ b/docs/sql_cheatsheet.md @@ -46,8 +46,7 @@ sqlite3 ### Trade table structure ```sql -CREATE TABLE trades -( +CREATE TABLE trades( id INTEGER NOT NULL, exchange VARCHAR NOT NULL, pair VARCHAR NOT NULL, From 50f0483d9aab4a0c73b6f7eb65d7babd3946817d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Sep 2020 20:00:36 +0200 Subject: [PATCH 22/22] FIx fluky test in test_api_logs --- tests/rpc/test_rpc_apiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index f8256f1ba..626586a4a 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -435,7 +435,7 @@ def test_api_logs(botclient): assert len(rc.json) == 2 assert 'logs' in rc.json # Using a fixed comparison here would make this test fail! - assert rc.json['log_count'] > 10 + assert rc.json['log_count'] > 1 assert len(rc.json['logs']) == rc.json['log_count'] assert isinstance(rc.json['logs'][0], list)