diff --git a/pandas_ta/momentum/bop.py b/pandas_ta/momentum/bop.py index 1c975ec..4679f63 100644 --- a/pandas_ta/momentum/bop.py +++ b/pandas_ta/momentum/bop.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..utils import get_offset, verify_series +from ..utils import get_offset, non_zero_range, verify_series def bop(open_, high, low, close, offset=None, **kwargs): """Indicator: Balance of Power (BOP)""" @@ -8,11 +8,11 @@ def bop(open_, high, low, close, offset=None, **kwargs): high = verify_series(high) low = verify_series(low) close = verify_series(close) + high_low_range = non_zero_range(high, low) + close_open_range = non_zero_range(close, open_) offset = get_offset(offset) # Calculate Result - close_open_range = close - open_ - high_low_range = high - low bop = close_open_range / high_low_range # Offset diff --git a/pandas_ta/momentum/rvi.py b/pandas_ta/momentum/rvi.py index 922be6b..92dc5db 100644 --- a/pandas_ta/momentum/rvi.py +++ b/pandas_ta/momentum/rvi.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from pandas import DataFrame from ..overlap.swma import swma -from ..utils import get_offset, verify_series +from ..utils import get_offset, non_zero_range, verify_series def rvi(open_, high, low, close, length=None, swma_length=None, offset=None, **kwargs): """Indicator: RVI""" @@ -10,13 +10,15 @@ def rvi(open_, high, low, close, length=None, swma_length=None, offset=None, **k high = verify_series(high) low = verify_series(low) close = verify_series(close) + high_low_range = non_zero_range(high, low) + close_open_range = non_zero_range(close, open_) length = int(length) if length and length > 0 else 14 swma_length = int(swma_length) if swma_length and swma_length > 0 else 4 offset = get_offset(offset) # Calculate Result - numerator = swma(close - open_, length=swma_length).rolling(length).sum() - denominator = swma(high - low, length=swma_length).rolling(length).sum() + numerator = swma(close_open_range, length=swma_length).rolling(length).sum() + denominator = swma(high_low_range, length=swma_length).rolling(length).sum() rvi = numerator / denominator signal = swma(rvi, length=swma_length) diff --git a/pandas_ta/overlap/kama.py b/pandas_ta/overlap/kama.py index a4b5f98..62ed5de 100644 --- a/pandas_ta/overlap/kama.py +++ b/pandas_ta/overlap/kama.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from numpy import NaN as npNaN -from pandas import Series -from ..utils import get_drift, get_offset, verify_series +from pandas import Series, DataFrame +from ..utils import get_drift, get_offset, non_zero_range, verify_series def kama(close, length=None, fast=None, slow=None, drift=None, offset=None, **kwargs): """Indicator: Kaufman's Adaptive Moving Average (HMA)""" @@ -17,9 +17,9 @@ def kama(close, length=None, fast=None, slow=None, drift=None, offset=None, **kw m = close.size fr = 2 / (fast + 1) sr = 2 / (slow + 1) - - abs_diff = close.diff(length).abs() - peer_diff = close.diff(drift).abs() + + abs_diff = non_zero_range(close, close.shift(length)).abs() + peer_diff = non_zero_range(close, close.shift(drift)).abs() peer_diff_sum = peer_diff.rolling(length).sum() er = abs_diff / peer_diff_sum x = er * (fr - sr) + sr diff --git a/pandas_ta/trend/qstick.py b/pandas_ta/trend/qstick.py index 8dbb4b4..3e5cbf1 100644 --- a/pandas_ta/trend/qstick.py +++ b/pandas_ta/trend/qstick.py @@ -4,7 +4,7 @@ from ..overlap.ema import ema from ..overlap.hma import hma from ..overlap.rma import rma from ..overlap.sma import sma -from ..utils import get_offset, verify_series +from ..utils import get_offset, non_zero_range, verify_series def qstick(open_, close, length=None, offset=None, **kwargs): """Indicator: Q Stick""" @@ -16,7 +16,7 @@ def qstick(open_, close, length=None, offset=None, **kwargs): ma = kwargs.pop('ma', 'sma') if 'ma' in kwargs else 'sma' # Calculate Result - diff = close - open_ + diff = non_zero_range(close, open_) if ma in [None, 'sma']: qstick = sma(diff, length=length) if ma == 'dema': qstick = dema(diff, length=length, **kwargs) diff --git a/pandas_ta/utils.py b/pandas_ta/utils.py index 6f3e907..a92243d 100644 --- a/pandas_ta/utils.py +++ b/pandas_ta/utils.py @@ -117,6 +117,17 @@ def get_offset(x:int): return int(x) if x else 0 +def non_zero_range(high:pd.Series, low:pd.Series): + """Returns the difference of two series and adds epsilon if + to any zero values. This occurs commonly in crypto data when + high = low. + """ + diff = high - low + if diff.eq(0).any().any(): + diff += sflt.epsilon + return diff + + def pascals_triangle(n:int =None, **kwargs): """Pascal's Triangle diff --git a/pandas_ta/volatility/accbands.py b/pandas_ta/volatility/accbands.py index 36317dc..82c6a0b 100644 --- a/pandas_ta/volatility/accbands.py +++ b/pandas_ta/volatility/accbands.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from pandas import DataFrame -from ..utils import get_drift, get_offset, verify_series +from ..utils import get_drift, get_offset, non_zero_range, verify_series def accbands(high, low, close, length=None, c=None, drift=None, mamode=None, offset=None, **kwargs): """Indicator: Acceleration Bands (ACCBANDS)""" @@ -8,6 +8,7 @@ def accbands(high, low, close, length=None, c=None, drift=None, mamode=None, off high = verify_series(high) low = verify_series(low) close = verify_series(close) + high_low_range = non_zero_range(high, low) length = int(length) if length and length > 0 else 20 c = float(c) if c and c > 0 else 4 min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length @@ -16,7 +17,7 @@ def accbands(high, low, close, length=None, c=None, drift=None, mamode=None, off offset = get_offset(offset) # Calculate Result - hl_ratio = (high - low) / (high + low) + hl_ratio = high_low_range / (high + low) hl_ratio *= c _lower = low * (1 - hl_ratio) _upper = high * (1 + hl_ratio) diff --git a/pandas_ta/volatility/kc.py b/pandas_ta/volatility/kc.py index deccae8..ae1a7d4 100644 --- a/pandas_ta/volatility/kc.py +++ b/pandas_ta/volatility/kc.py @@ -4,7 +4,7 @@ from pandas import DataFrame from .atr import atr from ..overlap.hlc3 import hlc3 from ..statistics.variance import variance -from ..utils import get_offset, verify_series +from ..utils import get_offset, non_zero_range, verify_series def kc(high, low, close, length=None, scalar=None, mamode=None, offset=None, **kwargs): @@ -26,7 +26,7 @@ def kc(high, low, close, length=None, scalar=None, mamode=None, offset=None, **k basis = close.ewm(span=length, min_periods=min_periods).mean() band = atr(high=high, low=low, close=close) else: - hl_range = high - low + hl_range = non_zero_range(high, low) typical_price = hlc3(high=high, low=low, close=close) basis = typical_price.rolling(length, min_periods=min_periods).mean() band = hl_range.rolling(length, min_periods=min_periods).mean() diff --git a/pandas_ta/volatility/massi.py b/pandas_ta/volatility/massi.py index b9c2b3e..ed6f5d9 100644 --- a/pandas_ta/volatility/massi.py +++ b/pandas_ta/volatility/massi.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- from ..overlap.ema import ema -from ..utils import get_offset, verify_series +from ..utils import get_offset, non_zero_range, verify_series def massi(high, low, fast=None, slow=None, offset=None, **kwargs): """Indicator: Mass Index (MASSI)""" # Validate arguments high = verify_series(high) low = verify_series(low) + high_low_range = non_zero_range(high, low) fast = int(fast) if fast and fast > 0 else 9 slow = int(slow) if slow and slow > 0 else 25 if slow < fast: @@ -15,8 +16,7 @@ def massi(high, low, fast=None, slow=None, offset=None, **kwargs): offset = get_offset(offset) # Calculate Result - hl_range = high - low - hl_ema1 = ema(close=hl_range, length=fast, **kwargs) + hl_ema1 = ema(close=high_low_range, length=fast, **kwargs) hl_ema2 = ema(close=hl_ema1, length=fast, **kwargs) hl_ratio = hl_ema1 / hl_ema2 diff --git a/pandas_ta/volatility/true_range.py b/pandas_ta/volatility/true_range.py index 4b62832..d8320ea 100644 --- a/pandas_ta/volatility/true_range.py +++ b/pandas_ta/volatility/true_range.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from pandas import DataFrame -from ..utils import get_drift, get_offset, verify_series +from ..utils import get_drift, get_offset, non_zero_range, verify_series def true_range(high, low, close, drift=None, offset=None, **kwargs): """Indicator: True Range""" @@ -8,12 +8,13 @@ def true_range(high, low, close, drift=None, offset=None, **kwargs): high = verify_series(high) low = verify_series(low) close = verify_series(close) + high_low_range = non_zero_range(high, low) drift = get_drift(drift) offset = get_offset(offset) # Calculate Result prev_close = close.shift(drift) - ranges = [high - low, high - prev_close, prev_close - low] + ranges = [high_low_range, high - prev_close, prev_close - low] true_range = DataFrame(ranges).T true_range = true_range.abs().max(axis=1) diff --git a/pandas_ta/volume/ad.py b/pandas_ta/volume/ad.py index ddacc55..08482a4 100644 --- a/pandas_ta/volume/ad.py +++ b/pandas_ta/volume/ad.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..utils import get_offset, verify_series +from ..utils import get_offset, non_zero_range, verify_series def ad(high, low, close, volume, open_=None, offset=None, **kwargs): """Indicator: Accumulation/Distribution (AD)""" @@ -8,17 +8,17 @@ def ad(high, low, close, volume, open_=None, offset=None, **kwargs): low = verify_series(low) close = verify_series(close) volume = verify_series(volume) + high_low_range = non_zero_range(high, low) offset = get_offset(offset) # Calculate Result if open_ is not None: open_ = verify_series(open_) - ad = close - open_ # AD with Open - else: - ad = 2 * close - high - low # AD with High, Low, Close - - hl_range = high - low - ad *= volume / hl_range + ad = non_zero_range(close, open_) # AD with Open + else: + ad = 2 * close - (high + low) # AD with High, Low, Close + + ad *= volume / high_low_range ad = ad.cumsum() # Offset diff --git a/pandas_ta/volume/cmf.py b/pandas_ta/volume/cmf.py index d7e7863..e861bcd 100644 --- a/pandas_ta/volume/cmf.py +++ b/pandas_ta/volume/cmf.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..utils import get_offset, verify_series +from ..utils import get_offset, non_zero_range, verify_series def cmf(high, low, close, volume, open_=None, length=None, offset=None, **kwargs): """Indicator: Chaikin Money Flow (CMF)""" @@ -8,6 +8,7 @@ def cmf(high, low, close, volume, open_=None, length=None, offset=None, **kwargs low = verify_series(low) close = verify_series(close) volume = verify_series(volume) + high_low_range = non_zero_range(high, low) length = int(length) if length and length > 0 else 20 min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length offset = get_offset(offset) @@ -15,11 +16,11 @@ def cmf(high, low, close, volume, open_=None, length=None, offset=None, **kwargs # Calculate Result if open_ is not None: open_ = verify_series(open_) - ad = close - open_ # AD with Open + ad = non_zero_range(close, open_) # AD with Open else: - ad = 2 * close - high - low # AD with High, Low, Close + ad = 2 * close - (high + low) # AD with High, Low, Close - hl_range = high - low + hl_range = high_low_range ad *= volume / hl_range cmf = ad.rolling(length, min_periods=min_periods).sum() / volume.rolling(length, min_periods=min_periods).sum() diff --git a/pandas_ta/volume/eom.py b/pandas_ta/volume/eom.py index c269493..0753283 100644 --- a/pandas_ta/volume/eom.py +++ b/pandas_ta/volume/eom.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from ..overlap.hl2 import hl2 -from ..utils import get_drift, get_offset, verify_series +from ..utils import get_drift, get_offset, non_zero_range, verify_series def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset=None, **kwargs): """Indicator: Ease of Movement (EOM)""" @@ -9,6 +9,7 @@ def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset= low = verify_series(low) close = verify_series(close) volume = verify_series(volume) + high_low_range = non_zero_range(high, low) length = int(length) if length and length > 0 else 14 min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length divisor = divisor if divisor and divisor > 0 else 100000000 @@ -16,9 +17,8 @@ def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset= offset = get_offset(offset) # Calculate Result - hl_range = high - low distance = hl2(high=high, low=low) - hl2(high=high.shift(drift), low=low.shift(drift)) - box_ratio = (volume / divisor) / hl_range + box_ratio = (volume / divisor) / high_low_range eom = distance / box_ratio eom = eom.rolling(length, min_periods=min_periods).mean() diff --git a/setup.py b/setup.py index 517d8d5..830953c 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ long_description = "An easy to use Python 3 Pandas Extension with 80+ Technical setup( name ="pandas_ta", packages =['pandas_ta', 'pandas_ta.momentum', 'pandas_ta.overlap', 'pandas_ta.performance', 'pandas_ta.statistics', 'pandas_ta.trend', 'pandas_ta.volatility', 'pandas_ta.volume'], - version ="0.1.38b", + version ="0.1.39b", description =long_description, long_description =long_description, author ="Kevin Johnson",