From 11cf9096c89d26846011f5a77c7389c6b3b581b6 Mon Sep 17 00:00:00 2001 From: laliux Date: Tue, 31 May 2022 18:39:19 -0500 Subject: [PATCH] Added new indicator SQZMOM --- README.md | 20 ++++++ app/analysis.py | 3 +- app/analyzers/indicators/__init__.py | 3 +- app/analyzers/indicators/sqzmom.py | 99 ++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 app/analyzers/indicators/sqzmom.py diff --git a/README.md b/README.md index 7b464bfe..be368b93 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Development branch to testing new features. This develop version has a lot of im - New indicator Klinger Oscillator - New indicator MACD Cross - New indicator StochRSI Cross +- New indicator Sqzmon - Squeeze Momentum Indicator - New option to customize ichimoku strategies and added chikou span @@ -522,6 +523,25 @@ indicators: mute_cold: false ``` +#### Squeeze Momentum Indicator + +It is a volatility and momentum indicator which capitalizes on the tendency for price to break out strongly after consolidating in a tight trading range. + +The volatility component measures price compression using Bollinger Bands and Keltner Channels. If the Bollinger Bands are completely enclosed within the Keltner Channels, that indicates a period of very low volatility. This state is known as the squeeze. When the Bollinger Bands expand and move back outside of the Keltner Channel, the squeeze is said to have “fired”: volatility increases and prices are likely to break out of that tight trading range in one direction or the other. The on/off state of the squeeze is shown with small dots on the zero line of the indicator: red dots indicate the squeeze is on, and green dots indicate the squeeze is off. + +For more details: https://www.tradingview.com/script/nqQ1DT5a-Squeeze-Momentum-Indicator-LazyBear/ + +``` +indicators: + sqzmom: + - enabled: true + alert_enabled: true + alert_frequency: once + candle_period: 1d + signal: + - close +``` + #### Chart images on webhook The config for a webhook notifier is the same as original CryptoSignal, no changes here, BUT the data sent in the request is completely different. diff --git a/app/analysis.py b/app/analysis.py index 824a73fd..2a7bc799 100644 --- a/app/analysis.py +++ b/app/analysis.py @@ -46,7 +46,8 @@ def indicator_dispatcher(self): 'bollinger': bollinger.Bollinger().analyze, 'bbp': bbp.BBP().analyze, 'macd_cross': macd_cross.MACDCross().analyze, - 'stochrsi_cross': stochrsi_cross.StochRSICross().analyze + 'stochrsi_cross': stochrsi_cross.StochRSICross().analyze, + 'sqzmom': sqzmom.SQZMOM().analyze } return dispatcher diff --git a/app/analyzers/indicators/__init__.py b/app/analyzers/indicators/__init__.py index 3d5130e2..6a3fe7c5 100644 --- a/app/analyzers/indicators/__init__.py +++ b/app/analyzers/indicators/__init__.py @@ -16,5 +16,6 @@ 'bollinger', 'bbp', 'macd_cross', - 'stochrsi_cross' + 'stochrsi_cross', + 'sqzmom' ] diff --git a/app/analyzers/indicators/sqzmom.py b/app/analyzers/indicators/sqzmom.py new file mode 100644 index 00000000..53beafcd --- /dev/null +++ b/app/analyzers/indicators/sqzmom.py @@ -0,0 +1,99 @@ +""" +Squeeze Momentum Indicator + +This indicator is based on this code: +https://www.tradingview.com/script/nqQ1DT5a-Squeeze-Momentum-Indicator-LazyBear/ + +by LazyBear https://www.tradingview.com/u/LazyBear/ + +""" + +import numpy as np + +from analyzers.utils import IndicatorUtils + + +class SQZMOM(IndicatorUtils): + + def analyze(self, historical_data, signal=['is_hot'], hot_thresh=None, cold_thresh=None): + """Performs a macd analysis on the historical data + + Args: + historical_data (list): A matrix of historical OHCLV data. + signal (list, optional): Defaults to macd + hot_thresh (float, optional): Unused for this indicator + cold_thresh (float, optional): Unused for this indicator + + Returns: + pandas.DataFrame: A dataframe containing the indicator and hot/cold values. + """ + + df = self.convert_to_dataframe(historical_data) + sqzmom = df.copy(deep=True); + + # parameter setup (default values in the original indicator) + length = 20 + mult = 2 + length_KC = 20 + mult_KC = 1.5 + + # calculate Bollinger Bands + + # moving average + m_avg = df['close'].rolling(window=length).mean() + # standard deviation + m_std = df['close'].rolling(window=length).std(ddof=0) + # upper Bollinger Bands + df['upper_BB'] = m_avg + mult * m_std + # lower Bollinger Bands + df['lower_BB'] = m_avg - mult * m_std + + # calculate Keltner Channel + + # first we need to calculate True Range + df['tr0'] = abs(df["high"] - df["low"]) + df['tr1'] = abs(df["high"] - df["close"].shift()) + df['tr2'] = abs(df["low"] - df["close"].shift()) + df['tr'] = df[['tr0', 'tr1', 'tr2']].max(axis=1) + # moving average of the TR + range_ma = df['tr'].rolling(window=length_KC).mean() + # upper Keltner Channel + df['upper_KC'] = m_avg + range_ma * mult_KC + # lower Keltner Channel + df['lower_KC'] = m_avg - range_ma * mult_KC + + # check for 'squeeze' + df['squeeze_on'] = (df['lower_BB'] > df['lower_KC']) & (df['upper_BB'] < df['upper_KC']) + df['squeeze_off'] = (df['lower_BB'] < df['lower_KC']) & (df['upper_BB'] > df['upper_KC']) + + # calculate momentum value + highest = df['high'].rolling(window = length_KC).max() + lowest = df['low'].rolling(window = length_KC).min() + m1 = (highest + lowest) / 2 + df['value'] = (df['close'] - (m1 + m_avg)/2) + fit_y = np.array(range(0,length_KC)) + df['value'] = df['value'].rolling(window = length_KC).apply(lambda x : np.polyfit(fit_y, x, 1)[0] * (length_KC-1) + + np.polyfit(fit_y, x, 1)[1], raw=True) + + + # entry point for long position: + # 1. black cross becomes gray (the squeeze is released) + long_cond1 = (df['squeeze_off'][-2] == False) & (df['squeeze_off'][-1] == True) + # 2. bar value is positive => the bar is light green + long_cond2 = df['value'][-1] > 0 + enter_long = long_cond1 and long_cond2 + + # entry point for short position: + # 1. black cross becomes gray (the squeeze is released) + short_cond1 = (df['squeeze_off'][-2] == False) & (df['squeeze_off'][-1] == True) + # 2. bar value is negative => the bar is light red + short_cond2 = df['value'][-1] < 0 + enter_short = short_cond1 and short_cond2 + + sqzmom['is_hot'] = False + sqzmom['is_cold'] = False + + sqzmom.at[sqzmom.index[-1], 'is_hot'] = enter_long + sqzmom.at[sqzmom.index[-1], 'is_cold'] = enter_short + + return sqzmom