mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-08-02 12:50:22 +08:00
ENH #92 + smi indicator MAINT refactoring
This commit is contained in:
+3
-3
@@ -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
|
||||
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user