TTM Squeeze PRO indicator twopirllc#313

This commit is contained in:
Zeel Patel
2021-06-24 16:33:09 -07:00
parent b2f2cc83a1
commit f666fd4e64
6 changed files with 270 additions and 1 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ Category = {
"ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo",
"coppock", "cti", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd",
"mom", "pgo", "ppo", "psl", "pvo", "qqe", "roc", "rsi", "rsx", "rvgi",
"slope", "smi", "squeeze", "stc", "stoch", "stochrsi", "td_seq", "trix",
"slope", "smi", "squeeze", "squeeze_pro", "stc", "stoch", "stochrsi", "td_seq", "trix",
"tsi", "uo", "willr"
],
# Overlap
+7
View File
@@ -1075,6 +1075,13 @@ class AnalysisIndicators(BasePandasObject):
result = squeeze(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar=kc_scalar, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, offset=offset, **kwargs)
return self._post_process(result, **kwargs)
def squeeze_pro(self, bb_length=None, bb_std=None, kc_length=None, kc_scalar_wide=None, kc_scalar_normal=None, kc_scalar_narrow=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs):
high = self._get_column(kwargs.pop("high", "high"))
low = self._get_column(kwargs.pop("low", "low"))
close = self._get_column(kwargs.pop("close", "close"))
result = squeeze_pro(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar_wide=kc_scalar_wide, kc_scalar_normal=kc_scalar_normal, kc_scalar_narrow=kc_scalar_narrow, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, offset=offset, **kwargs)
return self._post_process(result, **kwargs)
def stc(self, ma1=None, ma2=None, osc=None, tclength=None, fast=None, slow=None, factor=None, offset=None, **kwargs):
close = self._get_column(kwargs.pop("close", "close"))
result = stc(close=close, ma1=ma1, ma2=ma2, osc=osc, tclength=tclength, fast=fast, slow=slow, factor=factor, offset=offset, **kwargs)
+1
View File
@@ -30,6 +30,7 @@ from .rvgi import rvgi
from .slope import slope
from .smi import smi
from .squeeze import squeeze
from .squeeze_pro import squeeze_pro
from .stc import stc
from .stoch import stoch
from .stochrsi import stochrsi
+232
View File
@@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
from numpy import NaN as npNaN
from pandas import DataFrame
from pandas_ta.momentum import mom
from pandas_ta.overlap import ema, linreg, sma
from pandas_ta.trend import decreasing, increasing
from pandas_ta.volatility import bbands, kc
from pandas_ta.utils import get_offset
from pandas_ta.utils import unsigned_differences, verify_series
def squeeze_pro(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar_wide=None, kc_scalar_normal=None, kc_scalar_narrow=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs):
"""Indicator: Squeeze Momentum (SQZ) PRO"""
# Validate arguments
bb_length = int(bb_length) if bb_length and bb_length > 0 else 20
bb_std = float(bb_std) if bb_std and bb_std > 0 else 2.0
kc_length = int(kc_length) if kc_length and kc_length > 0 else 20
kc_scalar_wide = float(kc_scalar_wide) if kc_scalar_wide and kc_scalar_wide > 0 else 2
kc_scalar_normal = float(kc_scalar_normal) if kc_scalar_normal and kc_scalar_normal > 0 else 1.5
kc_scalar_narrow = float(kc_scalar_narrow) if kc_scalar_narrow and kc_scalar_narrow > 0 else 1
mom_length = int(mom_length) if mom_length and mom_length > 0 else 12
mom_smooth = int(mom_smooth) if mom_smooth and mom_smooth > 0 else 6
_length = max(bb_length, kc_length, mom_length, mom_smooth)
high = verify_series(high, _length)
low = verify_series(low, _length)
close = verify_series(close, _length)
offset = get_offset(offset)
valid_kc_scaler = kc_scalar_wide > kc_scalar_normal and kc_scalar_normal > kc_scalar_narrow
if not valid_kc_scaler: return
if high is None or low is None or close is None: return
use_tr = kwargs.setdefault("tr", True)
asint = kwargs.pop("asint", True)
detailed = kwargs.pop("detailed", False)
mamode = kwargs.pop("mamode", "sma").lower()
def simplify_columns(df, n=3):
df.columns = df.columns.str.lower()
return [c.split("_")[0][n - 1:n] for c in df.columns]
# Calculate Result
bbd = bbands(close, length=bb_length, std=bb_std, mamode=mamode)
kch_wide = kc(high, low, close, length=kc_length, scalar=kc_scalar_wide, mamode=mamode, tr=use_tr)
kch_normal = kc(high, low, close, length=kc_length, scalar=kc_scalar_normal, mamode=mamode, tr=use_tr)
kch_narrow = kc(high, low, close, length=kc_length, scalar=kc_scalar_narrow, mamode=mamode, tr=use_tr)
# Simplify KC and BBAND column names for dynamic access
bbd.columns = simplify_columns(bbd)
kch_wide.columns = simplify_columns(kch_wide)
kch_normal.columns = simplify_columns(kch_normal)
kch_narrow.columns = simplify_columns(kch_narrow)
momo = mom(close, length=mom_length)
if mamode.lower() == "ema":
squeeze = ema(momo, length=mom_smooth)
else: # "sma"
squeeze = sma(momo, length=mom_smooth)
# Classify Squeezes
squeeze_on_wide = (bbd.l > kch_wide.l) & (bbd.u < kch_wide.u)
squeeze_on_normal = (bbd.l > kch_normal.l) & (bbd.u < kch_normal.u)
squeeze_on_narrow = (bbd.l > kch_narrow.l) & (bbd.u < kch_narrow.u)
squeeze_off_wide = (bbd.l < kch_wide.l) & (bbd.u > kch_wide.u)
no_squeeze = ~squeeze_on_wide & ~squeeze_off_wide
# Offset
if offset != 0:
squeeze = squeeze.shift(offset)
squeeze_on_wide = squeeze_on_wide.shift(offset)
squeeze_on_normal = squeeze_on_normal.shift(offset)
squeeze_on_narrow = squeeze_on_narrow.shift(offset)
squeeze_off_wide = squeeze_off_wide.shift(offset)
no_squeeze = no_squeeze.shift(offset)
# Handle fills
if "fillna" in kwargs:
squeeze.fillna(kwargs["fillna"], inplace=True)
squeeze_on_wide.fillna(kwargs["fillna"], inplace=True)
squeeze_on_normal.fillna(kwargs["fillna"], inplace=True)
squeeze_on_narrow.fillna(kwargs["fillna"], inplace=True)
squeeze_off_wide.fillna(kwargs["fillna"], inplace=True)
no_squeeze.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
squeeze.fillna(method=kwargs["fill_method"], inplace=True)
squeeze_on_wide.fillna(method=kwargs["fill_method"], inplace=True)
squeeze_on_normal.fillna(method=kwargs["fill_method"], inplace=True)
squeeze_on_narrow.fillna(method=kwargs["fill_method"], inplace=True)
squeeze_off_wide.fillna(method=kwargs["fill_method"], inplace=True)
no_squeeze.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
_props = "" if use_tr else "hlr"
_props += f"_{bb_length}_{bb_std}_{kc_length}_{kc_scalar_wide}_{kc_scalar_normal}_{kc_scalar_narrow}"
squeeze.name = f"SQZPRO{_props}"
data = {
squeeze.name: squeeze,
f"SQZPRO_ON_WIDE": squeeze_on_wide.astype(int) if asint else squeeze_on_wide,
f"SQZPRO_ON_NORMAL": squeeze_on_normal.astype(int) if asint else squeeze_on_normal,
f"SQZPRO_ON_NARROW": squeeze_on_narrow.astype(int) if asint else squeeze_on_narrow,
f"SQZPRO_OFF": squeeze_off_wide.astype(int) if asint else squeeze_off_wide,
f"SQZPRO_NO": no_squeeze.astype(int) if asint else no_squeeze,
}
df = DataFrame(data)
df.name = squeeze.name
df.category = squeeze.category = "momentum"
# Detailed Squeeze Series
if detailed:
pos_squeeze = squeeze[squeeze >= 0]
neg_squeeze = squeeze[squeeze < 0]
pos_inc, pos_dec = unsigned_differences(pos_squeeze, asint=True)
neg_inc, neg_dec = unsigned_differences(neg_squeeze, asint=True)
pos_inc *= squeeze
pos_dec *= squeeze
neg_dec *= squeeze
neg_inc *= squeeze
pos_inc.replace(0, npNaN, inplace=True)
pos_dec.replace(0, npNaN, inplace=True)
neg_dec.replace(0, npNaN, inplace=True)
neg_inc.replace(0, npNaN, inplace=True)
sqz_inc = squeeze * increasing(squeeze)
sqz_dec = squeeze * decreasing(squeeze)
sqz_inc.replace(0, npNaN, inplace=True)
sqz_dec.replace(0, npNaN, inplace=True)
# Handle fills
if "fillna" in kwargs:
sqz_inc.fillna(kwargs["fillna"], inplace=True)
sqz_dec.fillna(kwargs["fillna"], inplace=True)
pos_inc.fillna(kwargs["fillna"], inplace=True)
pos_dec.fillna(kwargs["fillna"], inplace=True)
neg_dec.fillna(kwargs["fillna"], inplace=True)
neg_inc.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
sqz_inc.fillna(method=kwargs["fill_method"], inplace=True)
sqz_dec.fillna(method=kwargs["fill_method"], inplace=True)
pos_inc.fillna(method=kwargs["fill_method"], inplace=True)
pos_dec.fillna(method=kwargs["fill_method"], inplace=True)
neg_dec.fillna(method=kwargs["fill_method"], inplace=True)
neg_inc.fillna(method=kwargs["fill_method"], inplace=True)
df[f"SQZPRO_INC"] = sqz_inc
df[f"SQZPRO_DEC"] = sqz_dec
df[f"SQZPRO_PINC"] = pos_inc
df[f"SQZPRO_PDEC"] = pos_dec
df[f"SQZPRO_NDEC"] = neg_dec
df[f"SQZPRO_NINC"] = neg_inc
return df
squeeze_pro.__doc__ = \
"""Squeeze PRO(SQZPRO)
This indicator is an extended version of "TTM Squeeze" from John Carter.
The default is based on John Carter's "TTM Squeeze" indicator, as discussed
in his book "Mastering the Trade" (chapter 11). The Squeeze indicator attempts
to capture the relationship between two studies: Bollinger Bands® and Keltner's
Channels. When the volatility increases, so does the distance between the bands,
conversely, when the volatility declines, the distance also decreases. It finds
sections of the Bollinger Bands® study which fall inside the Keltner's Channels.
Sources:
https://usethinkscript.com/threads/john-carters-squeeze-pro-indicator-for-thinkorswim-free.4021/
https://www.tradingview.com/script/TAAt6eRX-Squeeze-PRO-Indicator-Makit0/
Calculation:
Default Inputs:
bb_length=20, bb_std=2, kc_length=20, kc_scalar_wide=2,
kc_scalar_normal=1.5, kc_scalar_narrow=1, mom_length=12,
mom_smooth=6, tr=True,
BB = Bollinger Bands
KC = Keltner Channels
MOM = Momentum
SMA = Simple Moving Average
EMA = Exponential Moving Average
TR = True Range
RANGE = TR(high, low, close) if using_tr else high - low
BB_LOW, BB_MID, BB_HIGH = BB(close, bb_length, std=bb_std)
KC_LOW_WIDE, KC_MID_WIDE, KC_HIGH_WIDE = KC(high, low, close, kc_length, kc_scalar_wide, TR)
KC_LOW_NORMAL, KC_MID_NORMAL, KC_HIGH_NORMAL = KC(high, low, close, kc_length, kc_scalar_normal, TR)
KC_LOW_NARROW, KC_MID_NARROW, KC_HIGH_NARROW = KC(high, low, close, kc_length, kc_scalar_narrow, TR)
MOMO = MOM(close, mom_length)
if mamode == "ema":
SQZPRO = EMA(MOMO, mom_smooth)
else:
SQZPRO = EMA(momo, mom_smooth)
SQZPRO_ON_WIDE = (BB_LOW > KC_LOW_WIDE) and (BB_HIGH < KC_HIGH_WIDE)
SQZPRO_ON_NORMAL = (BB_LOW > KC_LOW_NORMAL) and (BB_HIGH < KC_HIGH_NORMAL)
SQZPRO_ON_NARROW = (BB_LOW > KC_LOW_NARROW) and (BB_HIGH < KC_HIGH_NARROW)
SQZPRO_OFF_WIDE = (BB_LOW < KC_LOW_WIDE) and (BB_HIGH > KC_HIGH_WIDE)
SQZPRO_NO = !SQZ_ON_WIDE and !SQZ_OFF_WIDE
Args:
high (pd.Series): Series of 'high's
low (pd.Series): Series of 'low's
close (pd.Series): Series of 'close's
bb_length (int): Bollinger Bands period. Default: 20
bb_std (float): Bollinger Bands Std. Dev. Default: 2
kc_length (int): Keltner Channel period. Default: 20
kc_scalar_wide (float): Keltner Channel scalar for wider channel. Default: 2
kc_scalar_normal (float): Keltner Channel scalar for normal channel. Default: 1.5
kc_scalar_narrow (float): Keltner Channel scalar for narrow channel. Default: 1
mom_length (int): Momentum Period. Default: 12
mom_smooth (int): Smoothing Period of Momentum. Default: 6
mamode (str): Only "ema" or "sma". Default: "sma"
offset (int): How many periods to offset the result. Default: 0
Kwargs:
tr (value, optional): Use True Range for Keltner Channels. Default: True
asint (value, optional): Use integers instead of bool. Default: True
mamode (value, optional): Which MA to use. Default: "sma"
detailed (value, optional): Return additional variations of SQZ for
visualization. Default: False
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.DataFrame: SQZPRO, SQZPRO_ON_WIDE, SQZPRO_ON_NORMAL, SQZPRO_ON_NARROW, SQZPRO_OFF_WIDE, SQZPRO_NO columns by default. More
detailed columns if 'detailed' kwarg is True.
"""
+12
View File
@@ -202,6 +202,18 @@ class TestMomentumExtension(TestCase):
["SQZ_ON", "SQZ_OFF", "SQZ_NO", "SQZhlr_20_2.0_20_1.5"]
)
def test_squeeze_pro_ext(self):
self.data.ta.squeeze_pro(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-4:]), ["SQZPRO_ON_NORMAL", "SQZPRO_ON_NARROW", "SQZPRO_OFF", "SQZPRO_NO"])
self.data.ta.squeeze_pro(tr=False, append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(
list(self.data.columns[-4:]),
["SQZPRO_ON_NARROW", "SQZPRO_OFF", "SQZPRO_NO", "SQZPROhlr_20_2.0_20_2_1.5_1"]
)
def test_stc_ext(self):
self.data.ta.stc(append=True)
self.assertIsInstance(self.data, DataFrame)
+17
View File
@@ -354,6 +354,23 @@ class TestMomentum(TestCase):
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZhlr_20_2.0_20_1.5_LB")
def test_squeeze_pro(self):
result = pandas_ta.squeeze_pro(self.high, self.low, self.close)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZPRO_20_2.0_20_2_1.5_1")
result = pandas_ta.squeeze_pro(self.high, self.low, self.close, tr=False)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZPROhlr_20_2.0_20_2_1.5_1")
result = pandas_ta.squeeze_pro(self.high, self.low, self.close, 20, 2, 20, 3, 2, 1)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZPRO_20_2.0_20_3.0_2.0_1.0")
result = pandas_ta.squeeze_pro(self.high, self.low, self.close, 20, 2, 20, 3, 2, 1, tr=False)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZPROhlr_20_2.0_20_3.0_2.0_1.0")
def test_stc(self):
result = pandas_ta.stc(self.close)
self.assertIsInstance(result, DataFrame)