Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixing bug: min_periods when fillna=False #158

Merged
merged 5 commits into from May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion release.md
@@ -1,6 +1,7 @@
| Date | Version | Comment |
| ------------- | ------------- | ------------- |
| 2020/05/11 | 0.5.24 | Adding extra methods for IchimokuIndicator https://github.com/bukosabino/ta/pull/155 |
| 2020/05/12 | 0.5.25 | fixing bug: min_periods when fillna=False https://github.com/bukosabino/ta/pull/158 |
| 2020/05/11 | 0.5.24 | Adding extra methods for IchimokuIndicator https://github.com/bukosabino/ta/pull/156 |
| 2020/05/10 | 0.5.23 | Fixing bug when dataset with timestamp as index https://github.com/bukosabino/ta/pull/154 |
| 2020/05/04 | 0.5.22 | 1. Keltner Channel: adding tests; adding n atr input parametr; fixing some minor bug; adding unittests for adx https://github.com/bukosabino/ta/pull/148 |
| | | 2. Refactor tests code and speed up the tests https://github.com/bukosabino/ta/pull/149 |
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Expand Up @@ -4,7 +4,7 @@
setup(
name='ta',
packages=['ta'],
version='0.5.24',
version='0.5.25',
description='Technical Analysis Library in Python',
long_description='It is a Technical Analysis library to financial time series datasets. You can use to do feature engineering. It is builded on Python Pandas library.',
author='Dario Lopez Padial (Bukosabino)',
Expand All @@ -16,7 +16,7 @@
'numpy',
'pandas',
],
download_url='https://github.com/bukosabino/ta/tarball/0.5.24',
download_url='https://github.com/bukosabino/ta/tarball/0.5.25',
keywords=['technical analysis', 'python3', 'pandas'],
license='The MIT License (MIT)',
classifiers=[
Expand Down
48 changes: 32 additions & 16 deletions ta/momentum.py
Expand Up @@ -36,8 +36,9 @@ def _run(self):
diff = self._close.diff(1)
up = diff.where(diff > 0, 0.0)
dn = -diff.where(diff < 0, 0.0)
emaup = up.ewm(alpha=1/self._n, min_periods=0, adjust=False).mean()
emadn = dn.ewm(alpha=1/self._n, min_periods=0, adjust=False).mean()
min_periods = 0 if self._fillna else self._n
emaup = up.ewm(alpha=1/self._n, min_periods=min_periods, adjust=False).mean()
emadn = dn.ewm(alpha=1/self._n, min_periods=min_periods, adjust=False).mean()
rs = emaup / emadn
self._rsi = pd.Series(np.where(emadn == 0, 100, 100-(100/(1+rs))), index=self._close.index)

Expand Down Expand Up @@ -74,10 +75,12 @@ def __init__(self, close: pd.Series, r: int = 25, s: int = 13, fillna: bool = Fa

def _run(self):
m = self._close - self._close.shift(1)
m1 = m.ewm(span=self._r, min_periods=0, adjust=False).mean().ewm(
span=self._s, min_periods=0, adjust=False).mean()
m2 = abs(m).ewm(span=self._r, min_periods=0, adjust=False).mean().ewm(
span=self._s, min_periods=0, adjust=False).mean()
min_periods_r = 0 if self._fillna else self._r
min_periods_s = 0 if self._fillna else self._s
m1 = m.ewm(span=self._r, min_periods=min_periods_r, adjust=False).mean().ewm(
span=self._s, min_periods=min_periods_s, adjust=False).mean()
m2 = abs(m).ewm(span=self._r, min_periods=min_periods_r, adjust=False).mean().ewm(
span=self._s, min_periods=min_periods_s, adjust=False).mean()
self._tsi = m1 / m2
self._tsi *= 100

Expand Down Expand Up @@ -147,9 +150,15 @@ def _run(self):
cs = self._close.shift(1)
tr = self._true_range(self._high, self._low, cs)
bp = self._close - pd.DataFrame({'low': self._low, 'close': cs}).min(axis=1, skipna=False)
avg_s = bp.rolling(self._s, min_periods=self._s).sum() / tr.rolling(self._s, min_periods=self._s).sum()
avg_m = bp.rolling(self._m, min_periods=self._m).sum() / tr.rolling(self._m, min_periods=self._m).sum()
avg_l = bp.rolling(self._len, min_periods=self._len).sum() / tr.rolling(self._len, min_periods=self._len).sum()
min_periods_s = 0 if self._fillna else self._s
min_periods_m = 0 if self._fillna else self._m
min_periods_len = 0 if self._fillna else self._len
avg_s = bp.rolling(
self._s, min_periods=min_periods_s).sum() / tr.rolling(self._s, min_periods=min_periods_s).sum()
avg_m = bp.rolling(
self._m, min_periods=min_periods_m).sum() / tr.rolling(self._m, min_periods=min_periods_m).sum()
avg_l = bp.rolling(
self._len, min_periods=min_periods_len).sum() / tr.rolling(self._len, min_periods=min_periods_len).sum()
self._uo = (100.0 * ((self._ws * avg_s) + (self._wm * avg_m) + (self._wl * avg_l))
/ (self._ws + self._wm + self._wl))

Expand Down Expand Up @@ -198,8 +207,9 @@ def __init__(self,
self._run()

def _run(self):
smin = self._low.rolling(self._n, min_periods=0).min()
smax = self._high.rolling(self._n, min_periods=0).max()
min_periods = 0 if self._fillna else self._n
smin = self._low.rolling(self._n, min_periods=min_periods).min()
smax = self._high.rolling(self._n, min_periods=min_periods).max()
self._stoch_k = 100 * (self._close - smin) / (smax - smin)

def stoch(self) -> pd.Series:
Expand All @@ -217,7 +227,8 @@ def stoch_signal(self) -> pd.Series:
Returns:
pandas.Series: New feature generated.
"""
stoch_d = self._stoch_k.rolling(self._d_n, min_periods=0).mean()
min_periods = 0 if self._fillna else self._d_n
stoch_d = self._stoch_k.rolling(self._d_n, min_periods=min_periods).mean()
stoch_d = self._check_fillna(stoch_d, value=50)
return pd.Series(stoch_d, name='stoch_k_signal')

Expand Down Expand Up @@ -254,8 +265,9 @@ def _run(self):
close_values = self._close.values
vol = pd.Series(abs(self._close - np.roll(self._close, 1)))

min_periods = 0 if self._fillna else self._n
ER_num = abs(close_values - np.roll(close_values, self._n))
ER_den = vol.rolling(self._n).sum()
ER_den = vol.rolling(self._n, min_periods=min_periods).sum()
ER = ER_num / ER_den

sc = ((ER*(2.0/(self._pow1+1)-2.0/(self._pow2+1.0))+2/(self._pow2+1.0)) ** 2.0).values
Expand Down Expand Up @@ -372,7 +384,10 @@ def __init__(self, high: pd.Series, low: pd.Series, s: int = 5, len: int = 34, f

def _run(self):
mp = 0.5 * (self._high + self._low)
self._ao = mp.rolling(self._s, min_periods=0).mean() - mp.rolling(self._len, min_periods=0).mean()
min_periods_s = 0 if self._fillna else self._s
min_periods_len = 0 if self._fillna else self._len
self._ao = mp.rolling(
self._s, min_periods=min_periods_s).mean() - mp.rolling(self._len, min_periods=min_periods_len).mean()

def ao(self) -> pd.Series:
"""Awesome Oscillator
Expand Down Expand Up @@ -431,8 +446,9 @@ def __init__(self, high: pd.Series, low: pd.Series, close: pd.Series, lbp: int =
self._run()

def _run(self):
hh = self._high.rolling(self._lbp, min_periods=0).max() # highest high over lookback period lbp
ll = self._low.rolling(self._lbp, min_periods=0).min() # lowest low over lookback period lbp
min_periods = 0 if self._fillna else self._lbp
hh = self._high.rolling(self._lbp, min_periods=min_periods).max() # highest high over lookback period lbp
ll = self._low.rolling(self._lbp, min_periods=min_periods).min() # lowest low over lookback period lbp
self._wr = -100 * (hh - self._close) / (hh - ll)

def wr(self) -> pd.Series:
Expand Down
101 changes: 83 additions & 18 deletions ta/trend.py
Expand Up @@ -35,7 +35,8 @@ def __init__(self, close: pd.Series, n: int = 25, fillna: bool = False):
self._run()

def _run(self):
rolling_close = self._close.rolling(self._n, min_periods=0)
min_periods = 0 if self._fillna else self._n
rolling_close = self._close.rolling(self._n, min_periods=min_periods)
self._aroon_up = rolling_close.apply(
lambda x: float(np.argmax(x) + 1) / self._n * 100, raw=True)
self._aroon_down = rolling_close.apply(
Expand Down Expand Up @@ -245,11 +246,12 @@ def __init__(self, high: pd.Series, low: pd.Series, n: int = 9, n2: int = 25, fi
self._run()

def _run(self):
min_periods = 0 if self._fillna else self._n2
amplitude = self._high - self._low
ema1 = ema(amplitude, self._n, self._fillna)
ema2 = ema(ema1, self._n, self._fillna)
mass = ema1 / ema2
self._mass = mass.rolling(self._n2, min_periods=0).sum()
self._mass = mass.rolling(self._n2, min_periods=min_periods).sum()

def mass_index(self) -> pd.Series:
"""Mass Index (MI)
Expand Down Expand Up @@ -288,10 +290,14 @@ def __init__(self, high: pd.Series, low: pd.Series, n1: int = 9, n2: int = 26, n
self._run()

def _run(self):
min_periods_n1 = 0 if self._fillna else self._n1
min_periods_n2 = 0 if self._fillna else self._n2
self._conv = 0.5 * (
self._high.rolling(self._n1, min_periods=0).max() + self._low.rolling(self._n1, min_periods=0).min())
self._high.rolling(self._n1, min_periods=min_periods_n1).max() +
self._low.rolling(self._n1, min_periods=min_periods_n1).min())
self._base = 0.5 * (
self._high.rolling(self._n2, min_periods=0).max() + self._low.rolling(self._n2, min_periods=0).min())
self._high.rolling(self._n2, min_periods=min_periods_n2).max() +
self._low.rolling(self._n2, min_periods=min_periods_n2).min())

def ichimoku_conversion_line(self) -> pd.Series:
"""Tenkan-sen (Conversion Line)
Expand Down Expand Up @@ -376,14 +382,26 @@ def __init__(self, close: pd.Series, r1: int = 10, r2: int = 15, r3: int = 20, r
self._run()

def _run(self):
rocma1 = ((self._close - self._close.shift(self._r1, fill_value=self._close.mean()))
/ self._close.shift(self._r1, fill_value=self._close.mean())).rolling(self._n1, min_periods=0).mean()
rocma2 = ((self._close - self._close.shift(self._r2, fill_value=self._close.mean()))
/ self._close.shift(self._r2, fill_value=self._close.mean())).rolling(self._n2, min_periods=0).mean()
rocma3 = ((self._close - self._close.shift(self._r3, fill_value=self._close.mean()))
/ self._close.shift(self._r3, fill_value=self._close.mean())).rolling(self._n3, min_periods=0).mean()
rocma4 = ((self._close - self._close.shift(self._r4, fill_value=self._close.mean()))
/ self._close.shift(self._r4, fill_value=self._close.mean())).rolling(self._n4, min_periods=0).mean()
min_periods_n1 = 0 if self._fillna else self._n1
min_periods_n2 = 0 if self._fillna else self._n2
min_periods_n3 = 0 if self._fillna else self._n3
min_periods_n4 = 0 if self._fillna else self._n4
rocma1 = (
(self._close - self._close.shift(self._r1, fill_value=self._close.mean()))
/ self._close.shift(self._r1, fill_value=self._close.mean())).rolling(
self._n1, min_periods=min_periods_n1).mean()
rocma2 = (
(self._close - self._close.shift(self._r2, fill_value=self._close.mean()))
/ self._close.shift(self._r2, fill_value=self._close.mean())).rolling(
self._n2, min_periods=min_periods_n2).mean()
rocma3 = (
(self._close - self._close.shift(self._r3, fill_value=self._close.mean()))
/ self._close.shift(self._r3, fill_value=self._close.mean())).rolling(
self._n3, min_periods=min_periods_n3).mean()
rocma4 = (
(self._close - self._close.shift(self._r4, fill_value=self._close.mean()))
/ self._close.shift(self._r4, fill_value=self._close.mean())).rolling(
self._n4, min_periods=min_periods_n4).mean()
self._kst = 100 * (rocma1 + 2 * rocma2 + 3 * rocma3 + 4 * rocma4)
self._kst_sig = self._kst.rolling(self._nsig, min_periods=0).mean()

Expand Down Expand Up @@ -440,8 +458,9 @@ def __init__(self, close: pd.Series, n: int = 20, fillna: bool = False):
self._run()

def _run(self):
min_periods = 0 if self._fillna else self._n
self._dpo = (self._close.shift(int((0.5 * self._n) + 1), fill_value=self._close.mean())
- self._close.rolling(self._n, min_periods=0).mean())
- self._close.rolling(self._n, min_periods=min_periods).mean())

def dpo(self) -> pd.Series:
"""Detrended Price Oscillator (DPO)
Expand Down Expand Up @@ -493,9 +512,10 @@ def _run(self):
def _mad(x):
return np.mean(np.abs(x-np.mean(x)))

min_periods = 0 if self._fillna else self._n
pp = (self._high + self._low + self._close) / 3.0
self._cci = ((pp - pp.rolling(self._n, min_periods=0).mean())
/ (self._c * pp.rolling(self._n, min_periods=0).apply(_mad, True)))
self._cci = ((pp - pp.rolling(self._n, min_periods=min_periods).mean())
/ (self._c * pp.rolling(self._n, min_periods=min_periods).apply(_mad, True)))

def cci(self) -> pd.Series:
"""Commodity Channel Index (CCI)
Expand Down Expand Up @@ -660,11 +680,12 @@ def __init__(self, high: pd.Series, low: pd.Series, close: pd.Series, n: int = 1
def _run(self):
cs = self._close.shift(1, fill_value=self._close.mean())
tr = self._true_range(self._high, self._low, cs)
trn = tr.rolling(self._n).sum()
min_periods = 0 if self._fillna else self._n
trn = tr.rolling(self._n, min_periods=min_periods).sum()
vmp = np.abs(self._high - self._low.shift(1))
vmm = np.abs(self._low - self._high.shift(1))
self._vip = vmp.rolling(self._n, min_periods=0).sum() / trn
self._vin = vmm.rolling(self._n, min_periods=0).sum() / trn
self._vip = vmp.rolling(self._n, min_periods=min_periods).sum() / trn
self._vin = vmm.rolling(self._n, min_periods=min_periods).sum() / trn

def vortex_indicator_pos(self):
"""+VI
Expand Down Expand Up @@ -1197,6 +1218,50 @@ def kst_sig(close, r1=10, r2=15, r3=20, r4=30, n1=10, n2=10, n3=10, n4=15, nsig=
close=close, r1=r1, r2=r2, r3=r3, r4=r4, n1=n1, n2=n2, n3=n3, n4=n4, nsig=nsig, fillna=fillna).kst_sig()


def ichimoku_conversion_line(high, low, n1=9, n2=26, visual=False, fillna=False) -> pd.Series:
"""Tenkan-sen (Conversion Line)

It identifies the trend and look for potential signals within that trend.

http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ichimoku_cloud

Args:
high(pandas.Series): dataset 'High' column.
low(pandas.Series): dataset 'Low' column.
n1(int): n1 low period.
n2(int): n2 medium period.
visual(bool): if True, shift n2 values.
fillna(bool): if True, fill nan values.

Returns:
pandas.Series: New feature generated.
"""
return IchimokuIndicator(
high=high, low=low, n1=n1, n2=n2, n3=52, visual=visual, fillna=fillna).ichimoku_conversion_line()


def ichimoku_base_line(high, low, n1=9, n2=26, visual=False, fillna=False) -> pd.Series:
"""Kijun-sen (Base Line)

It identifies the trend and look for potential signals within that trend.

http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ichimoku_cloud

Args:
high(pandas.Series): dataset 'High' column.
low(pandas.Series): dataset 'Low' column.
n1(int): n1 low period.
n2(int): n2 medium period.
visual(bool): if True, shift n2 values.
fillna(bool): if True, fill nan values.

Returns:
pandas.Series: New feature generated.
"""
return IchimokuIndicator(
high=high, low=low, n1=n1, n2=n2, n3=52, visual=visual, fillna=fillna).ichimoku_base_line()


def ichimoku_a(high, low, n1=9, n2=26, visual=False, fillna=False):
"""Ichimoku Kinkō Hyō (Ichimoku)

Expand Down
13 changes: 7 additions & 6 deletions ta/volatility.py
Expand Up @@ -75,8 +75,9 @@ def __init__(self, close: pd.Series, n: int = 20, ndev: int = 2, fillna: bool =
self._run()

def _run(self):
self._mavg = self._close.rolling(self._n, min_periods=0).mean()
self._mstd = self._close.rolling(self._n, min_periods=0).std(ddof=0)
min_periods = 0 if self._fillna else self._n
self._mavg = self._close.rolling(self._n, min_periods=min_periods).mean()
self._mstd = self._close.rolling(self._n, min_periods=min_periods).std(ddof=0)
self._hband = self._mavg + self._ndev * self._mstd
self._lband = self._mavg - self._ndev * self._mstd

Expand Down Expand Up @@ -307,9 +308,9 @@ def __init__(
self._run()

def _run(self):
self.min_periods = 1 if self._fillna else self._n
self._hband = self._high.rolling(self._n, min_periods=self.min_periods).max()
self._lband = self._low.rolling(self._n, min_periods=self.min_periods).min()
self._min_periods = 1 if self._fillna else self._n
self._hband = self._high.rolling(self._n, min_periods=self._min_periods).max()
self._lband = self._low.rolling(self._n, min_periods=self._min_periods).min()

def donchian_channel_hband(self) -> pd.Series:
"""Donchian Channel High Band
Expand Down Expand Up @@ -351,7 +352,7 @@ def donchian_channel_wband(self) -> pd.Series:
Returns:
pandas.Series: New feature generated.
"""
mavg = self._close.rolling(self._n, min_periods=self.min_periods).mean()
mavg = self._close.rolling(self._n, min_periods=self._min_periods).mean()
wband = ((self._hband - self._lband) / mavg) * 100
wband = self._check_fillna(wband, value=0)
if self._offset != 0:
Expand Down
20 changes: 14 additions & 6 deletions ta/volume.py
Expand Up @@ -115,7 +115,10 @@ def _run(self):
mfv = ((self._close - self._low) - (self._high - self._close)) / (self._high - self._low)
mfv = mfv.fillna(0.0) # float division by zero
mfv *= self._volume
self._cmf = mfv.rolling(self._n, min_periods=0).sum() / self._volume.rolling(self._n, min_periods=0).sum()
min_periods = 0 if self._fillna else self._n
self._cmf = (
mfv.rolling(self._n, min_periods=min_periods).sum() /
self._volume.rolling(self._n, min_periods=min_periods).sum())

def chaikin_money_flow(self) -> pd.Series:
"""Chaikin Money Flow (CMF)
Expand Down Expand Up @@ -209,7 +212,8 @@ def sma_ease_of_movement(self) -> pd.Series:
Returns:
pandas.Series: New feature generated.
"""
emv = self._emv.rolling(self._n, min_periods=0).mean()
min_periods = 0 if self._fillna else self._n
emv = self._emv.rolling(self._n, min_periods=min_periods).mean()
emv = self._check_fillna(emv, value=0)
return pd.Series(emv, name=f'sma_eom_{self._n}')

Expand Down Expand Up @@ -335,8 +339,11 @@ def _run(self):
mf = tp * self._volume * up_down

# 4 positive and negative money flow with n periods
n_positive_mf = mf.rolling(self._n).apply(lambda x: np.sum(np.where(x >= 0.0, x, 0.0)), raw=True)
n_negative_mf = abs(mf.rolling(self._n).apply(lambda x: np.sum(np.where(x < 0.0, x, 0.0)), raw=True))
min_periods = 0 if self._fillna else self._n
n_positive_mf = mf.rolling(
self._n, min_periods=min_periods).apply(lambda x: np.sum(np.where(x >= 0.0, x, 0.0)), raw=True)
n_negative_mf = abs(
mf.rolling(self._n, min_periods=min_periods).apply(lambda x: np.sum(np.where(x < 0.0, x, 0.0)), raw=True))

# n_positive_mf = np.where(mf.rolling(self._n).sum() >= 0.0, mf, 0.0)
# n_negative_mf = abs(np.where(mf.rolling(self._n).sum() < 0.0, mf, 0.0))
Expand Down Expand Up @@ -401,10 +408,11 @@ def _run(self):
pv = (tp * self._volume)

# 3 total price * volume
total_pv = pv.rolling(self._n, min_periods=1).sum()
min_periods = 0 if self._fillna else self._n
total_pv = pv.rolling(self._n, min_periods=min_periods).sum()

# 4 total volume
total_volume = self._volume.rolling(self._n, min_periods=1).sum()
total_volume = self._volume.rolling(self._n, min_periods=min_periods).sum()

self.vwap = total_pv / total_volume

Expand Down