ENH #92 + smi indicator MAINT refactoring

This commit is contained in:
Kevin Johnson
2020-09-01 12:43:29 -07:00
parent 4804beaa38
commit a1df65746c
13 changed files with 188 additions and 57 deletions
+3 -3
View File
@@ -120,9 +120,9 @@ data/datas.csv
data/GLD_D_tv.csv
data/SPY_5min.csv
data/SPY_1min.csv
data/SPY_D_adxfish.csv
data/SPY_D_lbsz.csv
data/similang-ch.csv
data/SPY_D_TV1.csv
data/SPY_D_TV2.csv
data/SPY_D_TV3.csv
data/TV_5min.csv
data/tulip.csv
+3 -1
View File
@@ -58,6 +58,7 @@ and _Weighted Moving Average_.
## __New Indicators__
* _Squeeze_ (**squeeze**). A Momentum indicator. Both John Carter's TTM **and** Lazybear's TradingView versions are implemented. The default is John Carter's, or ```lazybear=False```. Set ```lazybear=True``` to enable Lazybear's.
* _TTM Trend_ (**ttm_trend**). A trend indicator inspired from John Carter's book "Mastering the Trade".
* _SMI Ergodic_ (**smi**) Developed by William Blau, the SMI Ergodic Indicator is the same as the True Strength Index (TSI) except the SMI includes a signal line and oscillator.
## __Updated Indicators__
* _Fisher Transform_ (**fisher**): Added Fisher's default **ema** signal line. To change the length of the signal line, use the argument: ```signal=5```. Default: 5
@@ -316,7 +317,7 @@ print(bothhl2.name) # "pre_HL2_post"
* _Doji_: **cdl_doji**
* _Heikin-Ashi_: **ha**
## _Momentum_ (31)
## _Momentum_ (32)
* _Awesome Oscillator_: **ao**
* _Absolute Price Oscillator_: **apo**
@@ -343,6 +344,7 @@ print(bothhl2.name) # "pre_HL2_post"
* _Relative Strength Index_: **rsi**
* _Relative Vigor Index_: **rvgi**
* _Slope_: **slope**
* _SMI Ergodic_ **smi**
* _Squeeze_: **squeeze**
* Default is John Carter's. Enable Lazybear's by ```lazybear=True```
* _Stochastic Oscillator_: **stoch**
+1 -1
View File
@@ -25,7 +25,7 @@ Category = {
"candles": ["cdl_doji", "ha"],
# Momentum
"momentum": ["ao", "apo", "bias", "bop", "brar", "cci", "cg", "cmo", "coppock", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd", "mom", "pgo", "ppo", "psl", "pvo", "roc", "rsi", "rvgi", "slope", "squeeze", "stoch", "trix", "tsi", "uo", "willr"],
"momentum": ["ao", "apo", "bias", "bop", "brar", "cci", "cg", "cmo", "coppock", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd", "mom", "pgo", "ppo", "psl", "pvo", "roc", "rsi", "rvgi", "slope", "smi", "squeeze", "stoch", "trix", "tsi", "uo", "willr"],
# Overlap
"overlap": ["dema", "ema", "fwma", "hl2", "hlc3", "hma", "ichimoku", "kama", "linreg", "midpoint", "midprice", "ohlc4", "pwma", "rma", "sinwma", "sma", "supertrend", "swma", "t3", "tema", "trima", "vwap", "vwma", "wcp", "wma", "zlma"],
+8 -8
View File
@@ -14,7 +14,7 @@ def cdl_doji(open_, high, low, close, length=None, factor=None, scalar=None, asi
factor = float(factor) if is_percent(factor) else 10
scalar = float(scalar) if scalar else 100
offset = get_offset(offset)
naive = kwargs.pop('naive', False)
naive = kwargs.pop("naive", False)
# Calculate Result
body = real_body(open_, close).abs()
@@ -32,10 +32,10 @@ def cdl_doji(open_, high, low, close, length=None, factor=None, scalar=None, asi
doji = doji.shift(offset)
# Handle fills
if 'fillna' in kwargs:
doji.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
doji.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
doji.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
doji.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
doji.name = f"CDL_DOJI_{length}_{0.01 * factor}"
@@ -69,9 +69,9 @@ Args:
high (pd.Series): Series of 'high's
low (pd.Series): Series of 'low's
close (pd.Series): Series of 'close's
length (int): The period. Default: 10
factor (float): Doji value. Default: 100
scalar (float): How much to magnify. Default: 100
length (int): The period. Default: 10
factor (float): Doji value. Default: 100
scalar (float): How much to magnify. Default: 100
asint (bool): Keep results numerical instead of boolean. Default: True
Kwargs:
+10 -3
View File
@@ -23,7 +23,7 @@ from pandas_ta.volatility import *
from pandas_ta.volume import *
from pandas_ta.utils import *
version = ".".join(("0", "1", "97b"))
version = ".".join(("0", "1", "98b"))
def mp_worker(args):
@@ -820,6 +820,13 @@ class AnalysisIndicators(BasePandasObject):
result = slope(close=close, length=length, offset=offset, **kwargs)
return result
@finalize
def smi(self, close=None, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs):
close = self._get_column(close, "close")
result = smi(close=close, fast=fast, slow=slow, signal=signal, scalar=scalar, offset=offset, **kwargs)
return result
@finalize
def squeeze(self, high=None, low=None, close=None, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs):
high = self._get_column(high, "high")
@@ -846,10 +853,10 @@ class AnalysisIndicators(BasePandasObject):
return result
@finalize
def tsi(self, close=None, fast=None, slow=None, drift=None, offset=None, **kwargs):
def tsi(self, close=None, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwargs):
close = self._get_column(close, "close")
result = tsi(close=close, fast=fast, slow=slow, drift=drift, offset=offset, **kwargs)
result = tsi(close=close, fast=fast, slow=slow, scalar=scalar, drift=drift, offset=offset, **kwargs)
return result
@finalize
+1
View File
@@ -24,6 +24,7 @@ from .roc import roc
from .rsi import rsi
from .rvgi import rvgi
from .slope import slope
from .smi import smi
from .squeeze import squeeze
from .stoch import stoch
from .trix import trix
+23 -23
View File
@@ -29,14 +29,14 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
signalma = signalma.shift(offset)
# Handle fills
if 'fillna' in kwargs:
macd.fillna(kwargs['fillna'], inplace=True)
histogram.fillna(kwargs['fillna'], inplace=True)
signalma.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
macd.fillna(method=kwargs['fill_method'], inplace=True)
histogram.fillna(method=kwargs['fill_method'], inplace=True)
signalma.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
macd.fillna(kwargs["fillna"], inplace=True)
histogram.fillna(kwargs["fillna"], inplace=True)
signalma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
macd.fillna(method=kwargs["fill_method"], inplace=True)
histogram.fillna(method=kwargs["fill_method"], inplace=True)
signalma.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
_props = f"_{fast}_{slow}_{signal}"
@@ -51,31 +51,31 @@ def macd(close, fast=None, slow=None, signal=None, offset=None, **kwargs):
df.name = f"MACD{_props}"
df.category = macd.category
signal_indicators = kwargs.pop('signal_indicators', False)
signal_indicators = kwargs.pop("signal_indicators", False)
if signal_indicators:
signalsdf = concat(
[
df,
signals(
indicator=histogram,
xa=kwargs.pop('xa', 0),
xb=kwargs.pop('xb', None),
xserie=kwargs.pop('xserie', None),
xserie_a=kwargs.pop('xserie_a', None),
xserie_b=kwargs.pop('xserie_b', None),
cross_values=kwargs.pop('cross_values', True),
cross_series=kwargs.pop('cross_series', True),
xa=kwargs.pop("xa", 0),
xb=kwargs.pop("xb", None),
xserie=kwargs.pop("xserie", None),
xserie_a=kwargs.pop("xserie_a", None),
xserie_b=kwargs.pop("xserie_b", None),
cross_values=kwargs.pop("cross_values", True),
cross_series=kwargs.pop("cross_series", True),
offset=offset,
),
signals(
indicator=macd,
xa=kwargs.pop('xa', 0),
xb=kwargs.pop('xb', None),
xserie=kwargs.pop('xserie', None),
xserie_a=kwargs.pop('xserie_a', None),
xserie_b=kwargs.pop('xserie_b', None),
cross_values=kwargs.pop('cross_values', False),
cross_series=kwargs.pop('cross_series', True),
xa=kwargs.pop("xa", 0),
xb=kwargs.pop("xb", None),
xserie=kwargs.pop("xserie", None),
xserie_a=kwargs.pop("xserie_a", None),
xserie_b=kwargs.pop("xserie_b", None),
cross_values=kwargs.pop("cross_values", False),
cross_series=kwargs.pop("cross_series", True),
offset=offset,
),
],
+98
View File
@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, concat
from .tsi import tsi
from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series, signals
def smi(close, fast=None, slow=None, signal=None, scalar=None, offset=None, **kwargs):
"""Indicator: SMI Ergodic Indicator (SMIIO)"""
# Validate arguments
close = verify_series(close)
fast = int(fast) if fast and fast > 0 else 5
slow = int(slow) if slow and slow > 0 else 20
signal = int(signal) if signal and signal > 0 else 5
if slow < fast:
fast, slow = slow, fast
scalar = float(scalar) if scalar else 1
offset = get_offset(offset)
# Calculate Result
smi = tsi(close, fast=fast, slow=slow, scalar=scalar)
signalma = ema(smi, signal)
osc = smi - signalma
# Offset
if offset != 0:
smi = smi.shift(offset)
signalma = signalma.shift(offset)
osc = osc.shift(offset)
# Handle fills
if "fillna" in kwargs:
smi.fillna(kwargs["fillna"], inplace=True)
signalma.fillna(kwargs["fillna"], inplace=True)
osc.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
smi.fillna(method=kwargs["fill_method"], inplace=True)
signalma.fillna(method=kwargs["fill_method"], inplace=True)
osc.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
_scalar = f"_{scalar}" if scalar != 1 else ""
_props = f"_{fast}_{slow}_{signal}{_scalar}"
smi.name = f"SMI{_props}"
signalma.name = f"SMIs{_props}"
osc.name = f"SMIo{_props}"
smi.category = signalma.category = osc.category = "momentum"
# Prepare DataFrame to return
data = {smi.name: smi, signalma.name: signalma, osc.name: osc}
df = DataFrame(data)
df.name = f"SMI{_props}"
df.category = smi.category
return df
smi.__doc__ = \
"""SMI Ergodic Indicator (SMI)
The SMI Ergodic Indicator is the same as the True Strength Index (TSI) developed
by William Blau, except the SMI includes a signal line. The SMI uses double
moving averages of price minus previous price over 2 time frames. The signal
line, which is an EMA of the SMI, is plotted to help trigger trading signals.
The trend is bullish when crossing above zero and bearish when crossing below
zero. This implementation includes both the SMI Ergodic Indicator and SMI
Ergodic Oscillator.
Sources:
https://www.motivewave.com/studies/smi_ergodic_indicator.htm
https://www.tradingview.com/script/Xh5Q0une-SMI-Ergodic-Oscillator/
https://www.tradingview.com/script/cwrgy4fw-SMIIO/
Calculation:
Default Inputs:
fast=5, slow=20, signal=5
TSI = True Strength Index
EMA = Exponential Moving Average
ERG = TSI(close, fast, slow)
Signal = EMA(ERG, signal)
OSC = ERG - Signal
Args:
close (pd.Series): Series of 'close's
fast (int): The short period. Default: 5
slow (int): The long period. Default: 20
signal (int): The signal period. Default: 5
scalar (float): How much to magnify. Default: 1
offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.DataFrame: smi, signal, oscillator columns.
"""
+18 -16
View File
@@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
from ..overlap.ema import ema
from ..utils import get_drift, get_offset, verify_series
from pandas_ta.overlap import ema
from pandas_ta.utils import get_drift, get_offset, verify_series
def tsi(close, fast=None, slow=None, drift=None, offset=None, **kwargs):
def tsi(close, fast=None, slow=None, scalar=None, drift=None, offset=None, **kwargs):
"""Indicator: True Strength Index (TSI)"""
# Validate Arguments
close = verify_series(close)
fast = int(fast) if fast and fast > 0 else 13
slow = int(slow) if slow and slow > 0 else 25
if slow < fast:
fast, slow = slow, fast
# if slow < fast:
# fast, slow = slow, fast
scalar = float(scalar) if scalar else 100
drift = get_drift(drift)
offset = get_offset(offset)
@@ -22,21 +23,21 @@ def tsi(close, fast=None, slow=None, drift=None, offset=None, **kwargs):
abs_slow_ema = ema(close=abs_diff, length=slow, **kwargs)
abs_fast_slow_ema = ema(close=abs_slow_ema, length=fast, **kwargs)
tsi = 100 * fast_slow_ema / abs_fast_slow_ema
tsi = scalar * fast_slow_ema / abs_fast_slow_ema
# Offset
if offset != 0:
tsi = tsi.shift(offset)
# Handle fills
if 'fillna' in kwargs:
tsi.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
tsi.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
tsi.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
tsi.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
tsi.name = f"TSI_{fast}_{slow}"
tsi.category = 'momentum'
tsi.category = "momentum"
return tsi
@@ -54,7 +55,7 @@ Sources:
Calculation:
Default Inputs:
fast=13, slow=25, drift=1
fast=13, slow=25, scalar=100, drift=1
EMA = Exponential Moving Average
diff = close.diff(drift)
@@ -64,13 +65,14 @@ Calculation:
abs_diff_slow_ema = absolute_diff_ema = EMA(ABS(diff), slow)
abema = abs_diff_fast_slow_ema = EMA(abs_diff_slow_ema, fast)
TSI = 100 * fast_slow_ema / abema
TSI = scalar * fast_slow_ema / abema
Args:
close (pd.Series): Series of 'close's
fast (int): The short period. Default: 13
slow (int): The long period. Default: 25
drift (int): The difference period. Default: 1
fast (int): The short period. Default: 13
slow (int): The long period. Default: 25
scalar (float): How much to magnify. Default: 100
drift (int): The difference period. Default: 1
offset (int): How many periods to offset the result. Default: 0
Kwargs:
+1 -1
View File
@@ -10,7 +10,7 @@ from functools import reduce
from operator import mul
from sys import float_info as sflt
TRADING_DAYS_PER_YEAR = 251
TRADING_DAYS_PER_YEAR = 252 # Keep even
TRADING_HOURS_PER_DAY = 6.5
MINUTES_PER_HOUR = 60
+1 -1
View File
@@ -2,7 +2,7 @@
from distutils.core import setup
from pandas_ta.core import version
long_description = "An easy to use Python 3 Pandas Extension with 100+ Technical Analysis Indicators. Can be called from a Pandas DataFrame or standalone like TA-Lib."
long_description = "An easy to use Python 3 Pandas Extension with 115+ Technical Analysis Indicators. Can be called from a Pandas DataFrame or standalone like TA-Lib. Correlation tested with TA-Lib."
setup(
name ="pandas_ta",
+12
View File
@@ -306,6 +306,18 @@ class TestMomentum(TestCase):
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "ANGLEd_1")
def test_smi(self):
result = pandas_ta.smi(self.close)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SMI_5_20_5")
self.assertEqual(len(result.columns), 3)
def test_smi_scalar(self):
result = pandas_ta.smi(self.close, scalar=10)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SMI_5_20_5_10.0")
self.assertEqual(len(result.columns), 3)
def test_squeeze(self):
result = pandas_ta.squeeze(self.high, self.low, self.close)
self.assertIsInstance(result, DataFrame)
+9
View File
@@ -166,6 +166,15 @@ class TestMomentumExtension(TestCase):
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "ANGLEd_1")
def test_smi_ext(self):
self.data.ta.smi(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-3:]), ["SMI_5_20_5", "SMIs_5_20_5", "SMIo_5_20_5"])
self.data.ta.smi(scalar=10, append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-3:]), ["SMI_5_20_5_10.0", "SMIs_5_20_5_10.0", "SMIo_5_20_5_10.0"])
def test_squeeze_ext(self):
self.data.ta.squeeze(append=True)
self.assertIsInstance(self.data, DataFrame)