diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0402a0d --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +init: + pip install -r requirements.txt + +test: + python3 -m unittest discover -v \ No newline at end of file diff --git a/README.md b/README.md index 9b90e93..1efa902 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,34 @@ help(pd.DataFrame().ta.log_return) # Technical Analysis Indicators (by Category) +## _Overlap_ (19) + +* _Double Exponential Moving Average_: **dema** +* _Exponential Moving Average_: **ema** +* _Fibonacci's Weighted Moving Average_: **fwma** +* _High-Low Average_: **hl2** +* _High-Low-Close Average_: **hlc3** + * Commonly known as 'Typical Price' in Technical Analysis literature +* _Hull Exponential Moving Average_: **hma** +* _Ichimoku Kinkō Hyō_: **ichimoku** + * Use: help(ta.ichimoku). Returns two DataFrames. +* _Midpoint_: **midpoint** +* _Midprice_: **midprice** +* _Open-High-Low-Close Average_: **ohlc4** +* _Pascal's Weighted Moving Average_: **pwma** +* _William's Moving Average_: **rma** +* _Simple Moving Average_: **sma** +* _T3 Moving Average_: **t3** +* _Triple Exponential Moving Average_: **tema** +* _Triangular Moving Average_: **trima** +* _Volume Weighted Average Price_: **vwap** +* _Volume Weighted Moving Average_: **vwma** +* _Weighted Moving Average_: **wma** + +| _Simple Moving Averages_ (SMA) and _Bollinger Bands_ (BBANDS) | +|:--------:| +| ![Example Chart](/images/TA_Chart.png) | + ## _Performance_ (2) Use parameter: cumulative=**True** for cumulative results. @@ -81,6 +109,7 @@ Use parameter: cumulative=**True** for cumulative results. # Inspiration -Inspired by Bukosabino: https://github.com/bukosabino/ta +* Original TA-LIB http://ta-lib.org/ +* Bukosabino: https://github.com/bukosabino/ta Please leave any comments, feedback, or suggestions. \ No newline at end of file diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 28bf427..745e3e9 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- name = "pandas_ta" """ .. moduleauthor:: Kevin Johnson diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 6c95491..c59b906 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -3,8 +3,9 @@ import time import pandas as pd from pandas.core.base import PandasObject -from .utils import * +from .overlap import * from .performance import * +from .utils import * class BasePandasObject(PandasObject): @@ -229,9 +230,159 @@ class AnalysisIndicators(BasePandasObject): else: print(s) - + # Overlap Indicators + def dema(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = dema(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def ema(self, close=None, length=None, offset=None, adjust=None, **kwargs): + close = self._get_column(close, 'close') + result = ema(close=close, length=length, offset=offset, adjust=adjust, **kwargs) + self._append(result, **kwargs) + return result + + + def fwma(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = fwma(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def hl2(self, high=None, low=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + result = hl2(high=high, low=low, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def hlc3(self, high=None, low=None, close=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + close = self._get_column(close, 'close') + result = hlc3(high=high, low=low, close=close, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def hma(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = hma(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def ichimoku(self, high=None, low=None, close=None, tenkan=None, kijun=None, senkou=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + close = self._get_column(close, 'close') + # result, span = ichimoku(high=high, low=low, close=close, tenkan=tenkan, kijun=kijun, senkou=senkou, offset=offset, **kwargs) + result = ichimoku(high=high, low=low, close=close, tenkan=tenkan, kijun=kijun, senkou=senkou, offset=offset, **kwargs) + self._append(result, **kwargs) + # return result, span + return result + + + def midpoint(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = midpoint(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def midprice(self, high=None, low=None, length=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + result = midprice(high=high, low=low, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def ohlc4(self, open_=None, high=None, low=None, close=None, offset=None, **kwargs): + open_ = self._get_column(open_, 'open') + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + close = self._get_column(close, 'close') + result = ohlc4(open_=open_, high=high, low=low, close=close, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def pwma(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = pwma(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def rma(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = rma(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def sma(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = sma(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def t3(self, close=None, length=None, a=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = t3(close=close, length=length, a=a, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def tema(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = tema(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def trima(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = trima(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def vwap(self, high=None, low=None, close=None, volume=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + close = self._get_column(close, 'close') + volume = self._get_column(volume, 'volume') + result = vwap(high=high, low=low, close=close, volume=volume, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def vwma(self, close=None, volume=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + volume = self._get_column(volume, 'volume') + result = vwma(close=close, volume=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + def wma(self, close=None, length=None, offset=None, **kwargs): + close = self._get_column(close, 'close') + result = wma(close=close, length=length, offset=offset, **kwargs) + self._append(result, **kwargs) + return result + + + + # Performance Indicators def log_return(self, close=None, length=None, cumulative=False, percent=False, offset=None, **kwargs): close = self._get_column(close, 'close') result = log_return(close=close, length=length, cumulative=cumulative, percent=percent, offset=offset, **kwargs) @@ -244,4 +395,4 @@ class AnalysisIndicators(BasePandasObject): close = self._get_column(close, 'close') result = percent_return(close=close, length=length, cumulative=cumulative, percent=percent, offset=offset, **kwargs) self._append(result, **kwargs) - return result \ No newline at end of file + return result diff --git a/pandas_ta/overlap.py b/pandas_ta/overlap.py new file mode 100644 index 0000000..7441c54 --- /dev/null +++ b/pandas_ta/overlap.py @@ -0,0 +1,1231 @@ +# -*- coding: utf-8 -*- +import math +import numpy as np +import pandas as pd + +# try: +# import scipy +# _SCIPY_ = True +# except ImportError: +# _SCIPY_ = False + +from .utils import fibonacci, pascals_triangle +from .utils import get_drift, get_offset, verify_series + + + +def weights(w): + def _compute(x): + return np.dot(w, x) + return _compute + + +def dema(close, length=None, offset=None, **kwargs): + """Indicator: Double Exponential Moving Average (DEMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + ema1 = ema(close=close, length=length, min_periods=min_periods) + ema2 = ema(close=ema1, length=length, min_periods=min_periods) + dema = 2 * ema1 - ema2 + + # Offset + if offset != 0: + dema = dema.shift(offset) + + # Name & Category + dema.name = f"DEMA_{length}" + dema.category = 'overlap' + + return dema + + +def ema(close, length=None, offset=None, **kwargs): + """Indicator: Exponential Moving Average (EMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length#int(0.25 * length) + adjust = bool(kwargs['adjust']) if 'adjust' in kwargs and kwargs['adjust'] is not None else True + offset = get_offset(offset) + + # Calculate Result + if 'presma' in kwargs and kwargs['presma']: + initial_sma = sma(close=close, length=length)[:length] + rest = close[length:] + close = pd.concat([initial_sma, rest]) + + ema = close.ewm(span=length, min_periods=min_periods, adjust=adjust).mean() + + # Offset + if offset != 0: + ema = ema.shift(offset) + + # Name & Category + ema.name = f"EMA_{length}" + ema.category = 'overlap' + + return ema + + +def fwma(close, length=None, asc=None, offset=None, **kwargs): + """Indicator: Fibonacci's Weighted Moving Average (FWMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + asc = asc if asc else True + offset = get_offset(offset) + + # Calculate Result + fibs = fibonacci(n=length, weighted=True) + fwma = close.rolling(length, min_periods=length).apply(weights(fibs), raw=True) + + # Offset + if offset != 0: + fwma = fwma.shift(offset) + + # Name & Category + fwma.name = f"FWMA_{length}" + fwma.category = 'overlap' + + return fwma + + +def hl2(high, low, offset=None, **kwargs): + """Indicator: HL2 """ + # Validate Arguments + high = verify_series(high) + low = verify_series(low) + offset = get_offset(offset) + + # Calculate Result + hl2 = 0.5 * (high + low) + + # Offset + if offset != 0: + hl2 = hl2.shift(offset) + + # Name & Category + hl2.name = "HL2" + hl2.category = 'overlap' + + return hl2 + + +def hlc3(high, low, close, offset=None, **kwargs): + """Indicator: HLC3""" + # Validate Arguments + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + offset = get_offset(offset) + + # Calculate Result + hlc3 = (high + low + close) / 3 + + # Offset + if offset != 0: + hlc3 = hlc3.shift(offset) + + # Name & Category + hlc3.name = "HLC3" + hlc3.category = 'overlap' + + return hlc3 + + +def hma(close, length=None, offset=None, **kwargs): + """Indicator: Hull Moving Average (HMA) + + Use help(df.ta.hma) for specific documentation where 'df' represents + the DataFrame you are using. + """ + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + half_length = int(length / 2) + sqrt_length = int(math.sqrt(length)) + + wmaf = wma(close=close, length=half_length) + wmas = wma(close=close, length=length) + hma = wma(close=2 * wmaf - wmas, length=sqrt_length) + + # Offset + if offset != 0: + hma = hma.shift(offset) + + # Name & Category + hma.name = f"HMA_{length}" + hma.category = 'overlap' + + return hma + + +def ichimoku(high, low, close, tenkan=None, kijun=None, senkou=None, offset=None, **kwargs): + """Indicator: Ichimoku Kinkō Hyō (Ichimoku)""" + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + tenkan = int(tenkan) if tenkan and tenkan > 0 else 9 + kijun = int(kijun) if kijun and kijun > 0 else 26 + senkou = int(senkou) if senkou and senkou > 0 else 52 + offset = get_offset(offset) + + # Calculate Result + tenkan_sen = midprice(high=high, low=low, length=tenkan) + kijun_sen = midprice(high=high, low=low, length=kijun) + span_a = 0.5 * (tenkan_sen + kijun_sen) + span_b = midprice(high=high, low=low, length=senkou) + + # Copy Span A and B values before their shift + _span_a = span_a[-kijun:].copy() + _span_b = span_b[-kijun:].copy() + + span_a = span_a.shift(kijun) + span_b = span_b.shift(kijun) + chikou_span = close.shift(-kijun) + + # Offset + if offset != 0: + tenkan_sen = tenkan_sen.shift(offset) + kijun_sen = kijun_sen.shift(offset) + span_a = span_a.shift(offset) + span_b = span_b.shift(offset) + chikou_span = chikou_span.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + span_a.fillna(kwargs['fillna'], inplace=True) + span_b.fillna(kwargs['fillna'], inplace=True) + chikou_span.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + span_a.fillna(method=kwargs['fill_method'], inplace=True) + span_b.fillna(method=kwargs['fill_method'], inplace=True) + chikou_span.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + span_a.name = f"ISA_{tenkan}" + span_b.name = f"ISB_{kijun}" + tenkan_sen.name = f"ITS_{tenkan}" + kijun_sen.name = f"IKS_{kijun}" + chikou_span.name = f"ICS_{kijun}" + + chikou_span.category = kijun_sen.category = tenkan_sen.category = 'trend' + span_b.category = span_a.category = chikou_span + + # Prepare Ichimoku DataFrame + data = {span_a.name: span_a, span_b.name: span_b, tenkan_sen.name: tenkan_sen, kijun_sen.name: kijun_sen, chikou_span.name: chikou_span} + ichimokudf = pd.DataFrame(data) + ichimokudf.name = f"ICHIMOKU_{tenkan}_{kijun}_{senkou}" + ichimokudf.category = 'overlap' + + # Prepare Span DataFrame, assuming it is a 'Daily' content + # Requires 'dates' from 'close' to work + if close.index.dtype == 'int64': + print(f"Integer indexing not implement yet") + else: + last_date = close.index[-1] + df_freq = close.index.value_counts().mode()[0] + tdelta = pd.Timedelta(df_freq, unit='d') + new_dt = pd.date_range(start=last_date + tdelta, periods=kijun, freq='B') + spandf = pd.DataFrame(index=new_dt, columns=[span_a.name, span_b.name]) + _span_a.index = _span_b.index = new_dt + spandf[span_a.name] = _span_a + spandf[span_b.name] = _span_b + + return ichimokudf, spandf + + return ichimokudf + + +def midpoint(close, length=None, offset=None, **kwargs): + """Indicator: Midpoint""" + # Validate arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 2 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + lowest = close.rolling(length, min_periods=min_periods).min() + highest = close.rolling(length, min_periods=min_periods).max() + midpoint = 0.5 * (lowest + highest) + + # Offset + if offset != 0: + midpoint = midpoint.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + midpoint.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + midpoint.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + midpoint.name = f"MIDPOINT_{length}" + midpoint.category = 'overlap' + + return midpoint + + +def midprice(high, low, length=None, offset=None, **kwargs): + """Indicator: Midprice""" + # Validate arguments + high = verify_series(high) + low = verify_series(low) + length = int(length) if length and length > 0 else 2 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + lowest_low = low.rolling(length, min_periods=min_periods).min() + highest_high = high.rolling(length, min_periods=min_periods).max() + midprice = 0.5 * (lowest_low + highest_high) + + # Offset + if offset != 0: + midprice = midprice.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + midprice.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + midprice.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + midprice.name = f"MIDPRICE_{length}" + midprice.category = 'overlap' + + return midprice + + +def ohlc4(open_, high, low, close, offset=None, **kwargs): + """Indicator: OHLC4""" + # Validate Arguments + open_ = verify_series(open_) + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + offset = get_offset(offset) + + # Calculate Result + ohlc4 = 0.25 * (open_ + high + low + close) + + # Offset + if offset != 0: + ohlc4 = ohlc4.shift(offset) + + # Name & Category + ohlc4.name = "OHLC4" + ohlc4.category = 'overlap' + + return ohlc4 + + +def pwma(close, length=None, asc=None, offset=None, **kwargs): + """Indicator: Pascals Weighted Moving Average (PWMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + asc = asc if asc else True + offset = get_offset(offset) + + # Calculate Result + triangle = pascals_triangle(n=length - 1, weighted=True) + pwma = close.rolling(length, min_periods=length).apply(weights(triangle), raw=True) + + # Offset + if offset != 0: + pwma = pwma.shift(offset) + + # Name & Category + pwma.name = f"PWMA_{length}" + pwma.category = 'overlap' + + return pwma + + +def rma(close, length=None, offset=None, **kwargs): + """Indicator: wildeR's Moving Average (RMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + alpha = (1.0 / length) if length > 0 else 1 + + # Calculate Result + rma = close.ewm(alpha=alpha, min_periods=min_periods).mean() + + # Offset + if offset != 0: + rma = rma.shift(offset) + + # Name & Category + rma.name = f"RMA_{length}" + rma.category = 'overlap' + + return rma + + +def sma(close, length=None, offset=None, **kwargs): + """Indicator: Simple Moving Average (SMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + sma = close.rolling(length, min_periods=min_periods).mean() + + # Offset + if offset != 0: + sma = sma.shift(offset) + + # Name & Category + sma.name = f"SMA_{length}" + sma.category = 'overlap' + + return sma + + +def t3(close, length=None, a=None, offset=None, **kwargs): + """Indicator: T3""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + a = float(a) if a and a > 0 and a < 1 else 0.7 + offset = get_offset(offset) + + # Calculate Result + c1 = -a * a ** 2 + c2 = 3 * a ** 2 + 3 * a ** 3 + c3 = -6 * a ** 2 - 3 * a - 3 * a ** 3 + c4 = a ** 3 + 3 * a ** 2 + 3 * a + 1 + + e1 = ema(close=close, length=length, min_periods=min_periods, **kwargs) + e2 = ema(close=e1, length=length, min_periods=min_periods, **kwargs) + e3 = ema(close=e2, length=length, min_periods=min_periods, **kwargs) + e4 = ema(close=e3, length=length, min_periods=min_periods, **kwargs) + e5 = ema(close=e4, length=length, min_periods=min_periods, **kwargs) + e6 = ema(close=e5, length=length, min_periods=min_periods, **kwargs) + t3 = c1 * e6 + c2 * e5 + c3 * e4 + c4 * e3 + + # Offset + if offset != 0: + t3 = t3.shift(offset) + + # Name & Category + t3.name = f"T3_{length}_{a}" + t3.category = 'overlap' + + return t3 + + +def tema(close, length=None, offset=None, **kwargs): + """Indicator: Triple Exponential Moving Average (TEMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + ema1 = ema(close=close, length=length, min_periods=min_periods) + ema2 = ema(close=ema1, length=length, min_periods=min_periods) + ema3 = ema(close=ema2, length=length, min_periods=min_periods) + tema = 3 * (ema1 - ema2) + ema3 + + # Offset + if offset != 0: + tema = tema.shift(offset) + + # Name & Category + tema.name = f"TEMA_{length}" + tema.category = 'overlap' + + return tema + + +def trima(close, length=None, offset=None, **kwargs): + """Indicator: Triangular Moving Average (TRIMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + half_length = round(0.5 * (length + 1)) + sma1 = close.rolling(half_length, min_periods=half_length).mean() + trima = sma1.rolling(half_length, min_periods=half_length).mean() + + # Offset + if offset != 0: + trima = trima.shift(offset) + + # Name & Category + trima.name = f"TRIMA_{length}" + trima.category = 'overlap' + + return trima + + +def vwap(high, low, close, volume, offset=None, **kwargs): + """Indicator: Volume Weighted Average Price (VWAP)""" + # Validate Arguments + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + volume = verify_series(volume) + offset = get_offset(offset) + + # Calculate Result + tp = hlc3(high=high, low=low, close=close) + tpv = tp * volume + vwap = tpv.cumsum() / volume.cumsum() + + # Offset + if offset != 0: + vwap = vwap.shift(offset) + + # Name & Category + vwap.name = "VWAP" + vwap.category = 'overlap' + + return vwap + + +def vwma(close, volume, length=None, offset=None, **kwargs): + """Indicator: Volume Weighted Moving Average (VWMA)""" + # Validate Arguments + close = verify_series(close) + volume = verify_series(volume) + length = int(length) if length and length > 0 else 10 + offset = get_offset(offset) + + # Calculate Result + pv = close * volume + vwma = sma(close=pv, length=length) / sma(close=volume, length=length) + + # Offset + if offset != 0: + vwma = vwma.shift(offset) + + # Name & Category + vwma.name = f"VWMA_{length}" + vwma.category = 'overlap' + + return vwma + + +def wma(close, length=None, asc=None, offset=None, **kwargs): + """Indicator: Weighted Moving Average (WMA)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + asc = asc if asc else True + offset = get_offset(offset) + + # Calculate Result + total_weight = 0.5 * length * (length + 1) + weights_ = pd.Series(np.arange(1, length + 1)) + weights = weights_ if asc else weights_[::-1] + + def linear_weights(w): + def _compute(x): + return (w * x).sum() / total_weight + return _compute + + close_ = close.rolling(length, min_periods=length) + wma = close_.apply(linear_weights(weights), raw=True) + + # Offset + if offset != 0: + wma = wma.shift(offset) + + # Name & Category + wma.name = f"WMA_{length}" + wma.category = 'overlap' + + return wma + + + +# Overlap Documentation +hl2.__doc__ = \ +"""Average of High-Low (HL2) + +Equally weighted Average of two series', namely High and Low. + +Sources: + https://www.tradingview.com/study-script-reference/#var_hl2 + +Calculation: + HL2 = 0.5 * (high + low) + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + 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.Series: New feature generated. +""" + + +ichimoku.__doc__ = \ +"""Ichimoku Kinkō Hyō (Ichimoku) + +It identifies the trend and look for potential signals within that trend. + +Sources: + http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ichimoku_cloud + +Calculation: + Default Inputs: + tenkan=9, kijun=26, senkou=52 + tenkan_sen = midprice(high, low, length=tenkan) + kijun_sen = midprice(high, low, length=kijun) + span_a = 0.5 * (tenkan_sen + kijun_sen) + span_b = midprice(high=high, low=low, length=senkou) + + span_a = span_a.shift(kijun) + span_b = span_b.shift(kijun) + chikou_span = close.shift(-kijun) + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + tenkan (int): Tenkan period. Default: 9 + kijun (int): Kijun period. Default: 26 + senkou (int): Senkou period. Default: 52 + 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: + pandas.Series: New feature generated. +""" + + + +hlc3.__doc__ = \ +"""Average of High-Low-Close (HLC3) + +Equally weighted Average of three series', namely High, Low, Close. + +Sources: + https://www.tradingview.com/study-script-reference/#var_hlc3 + +Calculation: + HLC3 = (high + low + close) / 3.0 + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + 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.Series: New feature generated. +""" + + +ohlc4.__doc__ = \ +"""Average of Open-High-Low-Close (OHLC4) + +Equally weighted Average of four series', namely Open, High, Low, Close. + +Sources: + https://www.tradingview.com/study-script-reference/#var_ohlc4 + +Calculation: + OHLC4 = 0.25 * (open + high + low + close) + +Args: + open (pd.Series): Series of 'open's + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + 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.Series: New feature generated. +""" + + +midpoint.__doc__ = \ +"""Midpoint (MIDPOINT) + +The Midpoint is the average of the highest and lowest closes over a period. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/midpoint-midpnt/ + +Calculation: + Default Inputs: + length=1 + lowest_close = close.rolling(length).min() + highest_close = close.rolling(length).max() + + MIDPOINT = 0.5 * (highest_close + lowest_close) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 14 + 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.Series: New feature generated. +""" + + +midprice.__doc__ = \ +"""Midprice (MIDPRICE) + +William's Percent R is a momentum oscillator similar to the RSI that +attempts to identify overbought and oversold conditions. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/midprice-midpri/ + +Calculation: + Default Inputs: + length=1 + lowest_low = low.rolling(length).min() + highest_high = high.rolling(length).max() + + MIDPRICE = 0.5 * (highest_high + lowest_low) + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + length (int): It's period. 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.Series: New feature generated. +""" + + +dema.__doc__ = \ +"""Double Exponential Moving Average (DEMA) + +The Double Exponential Moving Average attempts to a smoother average with less +lag than the normal Exponential Moving Average (EMA). + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/double-exponential-moving-average-dema/ + +Calculation: + Default Inputs: + length=10 + EMA = Exponential Moving Average + ema1 = EMA(close, length) + ema2 = EMA(ema1, length) + + DEMA = 2 * ema1 - ema2 + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + 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.Series: New feature generated. +""" + + +ema.__doc__ = \ +"""Exponential Moving Average (EMA) + +The Exponential Moving Average is more responsive moving average compared to the +Simple Moving Average (SMA). The weights are determined by alpha which is +proportional to it's length. There are several different methods of calculating +EMA. One method uses just the standard definition of EMA and another uses the +SMA to generate the initial value for the rest of the calculation. + +Sources: + https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages + https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp + +Calculation: + Default Inputs: + length=10 + SMA = Simple Moving Average + if kwargs['presma']: + initial = SMA(close, length) + rest = close[length:] + close = initial + rest + + EMA = close.ewm(span=length, adjust=adjust).mean() + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + adjust (bool): Default: True + presma (bool, optional): If True, uses SMA for initial value. + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + +Returns: + pd.Series: New feature generated. +""" + + +fwma.__doc__ = \ +"""Fibonacci's Weighted Moving Average (FWMA) + +Fibonacci's Weighted Moving Average is similar to a Weighted Moving Average +(WMA) where the weights are based on the Fibonacci Sequence. + +Source: Kevin Johnson + +Calculation: + Default Inputs: + length=10, + + def weights(w): + def _compute(x): + return np.dot(w * x) + return _compute + + fibs = utils.fibonacci(length - 1) + FWMA = close.rolling(length)_.apply(weights(fibs), raw=True) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + asc (bool): Recent values weigh more. Default: True + 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.Series: New feature generated. +""" + + +hma.__doc__ = \ +"""Hull Moving Average (HMA) + +The Hull Exponential Moving Average attempts to reduce or remove lag in moving +averages. + +Sources: + https://alanhull.com/hull-moving-average + +Calculation: + Default Inputs: + length=10 + WMA = Weighted Moving Average + half_length = int(0.5 * length) + sqrt_length = int(math.sqrt(length)) + + wmaf = WMA(close, half_length) + wmas = WMA(close, length) + HMA = WMA(2 * wmaf - wmas, sqrt_length) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + 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.Series: New feature generated. +""" + + +ichimoku.__doc__ = \ +"""Ichimoku Kinkō Hyō (ichimoku) + +Developed Pre WWII as a forecasting model for financial markets. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/ichimoku-ich/ + +Calculation: + Default Inputs: + tenkan=9, kijun=26, senkou=52 + MIDPRICE = Midprice + TENKAN_SEN = MIDPRICE(high, low, close, length=tenkan) + KIJUN_SEN = MIDPRICE(high, low, close, length=kijun) + CHIKOU_SPAN = close.shift(-kijun) + + SPAN_A = 0.5 * (TENKAN_SEN + KIJUN_SEN) + SPAN_A = SPAN_A.shift(kijun) + + SPAN_B = MIDPRICE(high, low, close, length=senkou) + SPAN_B = SPAN_B.shift(kijun) + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + tenkan (int): Tenkan period. Default: 9 + kijun (int): Kijun period. Default: 26 + senkou (int): Senkou period. Default: 52 + 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: Two DataFrames. + For the visible period: spanA, spanB, tenkan_sen, kijun_sen, + and chikou_span columns + For the forward looking period: spanA and spanB columns +""" + + +pwma.__doc__ = \ +"""Pascal's Weighted Moving Average (PWMA) + +Pascal's Weighted Moving Average is similar to a symmetric triangular +window except PWMA's weights are based on Pascal's Triangle. + +Source: Kevin Johnson + +Calculation: + Default Inputs: + length=10, + + def weights(w): + def _compute(x): + return np.dot(w * x) + return _compute + + triangle = utils.pascals_triangle(length + 1) + PWMA = close.rolling(length)_.apply(weights(triangle), raw=True) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + asc (bool): Recent values weigh more. Default: True + 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.Series: New feature generated. +""" + + +rma.__doc__ = \ +"""wildeR's Moving Average (RMA) + +The WildeR's Moving Average is simply an Exponential Moving Average (EMA) +with a modified alpha = 1 / length. + +Sources: + https://alanhull.com/hull-moving-average + +Calculation: + Default Inputs: + length=10 + EMA = Exponential Moving Average + alpha = 1 / length + RMA = EMA(close, alpha=alpha) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + 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.Series: New feature generated. +""" + + +sma.__doc__ = \ +"""Simple Moving Average (SMA) + +The Simple Moving Average is the classic moving average that is the equally +weighted average over n periods. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/simple-moving-average-sma/ + +Calculation: + Default Inputs: + length=10 + SMA = SUM(close, length) / length + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + adjust (bool): Default: True + presma (bool, optional): If True, uses SMA for initial value. + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + +Returns: + pd.Series: New feature generated. +""" + + +t3.__doc__ = \ +"""Tim Tillson's T3 Moving Average (T3) + +Tim Tillson's T3 Moving Average is considered a smoother and more responsive +moving average relative to other moving averages. + +Sources: + http://www.binarytribune.com/forex-trading-indicators/t3-moving-average-indicator/ + +Calculation: + Default Inputs: + length=10, a=0.7 + c1 = -a^3 + c2 = 3a^2 + 3a^3 = 3a^2 * (1 + a) + c3 = -6a^2 - 3a - 3a^3 + c4 = a^3 + 3a^2 + 3a + 1 + + ema1 = EMA(close, length) + ema2 = EMA(ema1, length) + ema3 = EMA(ema2, length) + ema4 = EMA(ema3, length) + ema5 = EMA(ema4, length) + ema6 = EMA(ema5, length) + T3 = c1 * ema6 + c2 * ema5 + c3 * ema4 + c4 * ema3 + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + a (float): 0 < a < 1. Default: 0.7 + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + adjust (bool): Default: True + presma (bool, optional): If True, uses SMA for initial value. + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + +Returns: + pd.Series: New feature generated. +""" + + +tema.__doc__ = \ +"""Triple Exponential Moving Average (TEMA) + +A less laggy Exponential Moving Average. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/triple-exponential-moving-average-tema/ + +Calculation: + Default Inputs: + length=10 + EMA = Exponential Moving Average + ema1 = EMA(close, length) + ema2 = EMA(ema1, length) + ema3 = EMA(ema2, length) + TEMA = 3 * (ema1 - ema2) + ema3 + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + adjust (bool): Default: True + presma (bool, optional): If True, uses SMA for initial value. + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + +Returns: + pd.Series: New feature generated. +""" + + +trima.__doc__ = \ +"""Triangular Moving Average (TRIMA) + +A weighted moving average where the shape of the weights are triangular and the +greatest weight is in the middle of the period. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/triangular-moving-average-trima/ + +Calculation: + Default Inputs: + length=10 + SMA = Simple Moving Average + half_length = math.round(0.5 * (length + 1)) + SMA1 = SMA(close, half_length) + TRIMA = SMA(SMA1, half_length) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + offset (int): How many periods to offset the result. Default: 0 + +Kwargs: + adjust (bool): Default: True + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + +Returns: + pd.Series: New feature generated. +""" + + +vwap.__doc__ = \ +"""Volume Weighted Average Price (VWAP) + +The Volume Weighted Average Price that measures the average typical price +by volume. It is typically used with intraday charts to identify general +direction. + +Sources: + https://www.tradingview.com/wiki/Volume_Weighted_Average_Price_(VWAP) + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/volume-weighted-average-price-vwap/ + +Calculation: + tp = typical_price = hlc3(high, low, close) + tpv = tp * volume + VWAP = tpv.cumsum() / volume.cumsum() + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + volume (pd.Series): Series of 'volume's + 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.Series: New feature generated. +""" + + +vwma.__doc__ = \ +"""Volume Weighted Moving Average (VWMA) + +Volume Weighted Moving Average. + +Sources: + https://www.motivewave.com/studies/volume_weighted_moving_average.htm + +Calculation: + Default Inputs: + length=10 + SMA = Simple Moving Average + pv = close * volume + VWMA = SMA(pv, length) / SMA(volume, length) + +Args: + close (pd.Series): Series of 'close's + volume (pd.Series): Series of 'volume's + length (int): It's period. Default: 10 + 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.Series: New feature generated. +""" + + +wma.__doc__ = \ +"""Weighted Moving Average (WMA) + +The Weighted Moving Average where the weights are linearly increasing and +the most recent data has the heaviest weight. + +Sources: + https://en.wikipedia.org/wiki/Moving_average#Weighted_moving_average + +Calculation: + Default Inputs: + length=10, asc=True + total_weight = 0.5 * length * (length + 1) + weights_ = [1, 2, ..., length + 1] # Ascending + weights = weights if asc else weights[::-1] + + def linear_weights(w): + def _compute(x): + return (w * x).sum() / total_weight + return _compute + + WMA = close.rolling(length)_.apply(linear_weights(weights), raw=True) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 10 + asc (bool): Recent values weigh more. Default: True + 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.Series: New feature generated. +""" diff --git a/pandas_ta/utils.py b/pandas_ta/utils.py index 03ff30f..40fc7f9 100644 --- a/pandas_ta/utils.py +++ b/pandas_ta/utils.py @@ -27,10 +27,29 @@ def combination(**kwargs): return numerator // denominator +def df_error_analysis(dfA, dfB, **kwargs): + """ """ + corr_method = kwargs.pop('corr_method', 'pearson') + + # Find their differences + diff = dfA - dfB + df = pd.DataFrame({'diff': diff.describe()}) + extra = pd.DataFrame([diff.var(), diff.mad(), diff.sem(), dfA.corr(dfB, method=corr_method)], index=['var', 'mad', 'sem', 'corr']) + + # Append the differences to the DataFrame + df = df['diff'].append(extra, ignore_index=False)[0] + + # For plotting + # diff.hist() + # if diff[diff > 0].any(): + # diff.plot(kind='kde') + + return df + def fibonacci(**kwargs): """Fibonacci Sequence as a numpy array""" n = int(math.fabs(kwargs.pop('n', 2))) - zero = kwargs.pop('zero', True) + zero = kwargs.pop('zero', False) weighted = kwargs.pop('weighted', False) if zero: @@ -71,6 +90,7 @@ def pascals_triangle(**kwargs): """ n = int(math.fabs(kwargs.pop('n', 0))) weighted = kwargs.pop('weighted', False) + inverse = kwargs.pop('inverse', False) sink = kwargs.pop('all', False) # Calculation @@ -88,6 +108,11 @@ def pascals_triangle(**kwargs): if sink: return triangle, triangle_sum, triangle_avg, inverted, weights, inv_weights, triangle_avg if weighted: + # Needs to be fixed: n=3 => [1, 2, 1], t=4 + # weighted: [1/4, 2/4, 1/4] + # + # if inverse: + # return inv_weights return weights else: return triangle diff --git a/setup.py b/setup.py index 0c63c86..1bcd021 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ long_description = "A Python 3 Pandas Extension of Technical Analysis Indicators setup( name = "pandas_ta", packages = ["pandas_ta"], - version = "0.0.4a", + version = "0.0.5a", description=long_description, long_description=long_description, author = "Kevin Johnson", diff --git a/tests/context.py b/tests/context.py new file mode 100644 index 0000000..a237851 --- /dev/null +++ b/tests/context.py @@ -0,0 +1,5 @@ +import os +import sys +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +import pandas_ta \ No newline at end of file diff --git a/tests/data.py b/tests/data.py new file mode 100644 index 0000000..8c4549b --- /dev/null +++ b/tests/data.py @@ -0,0 +1,4 @@ +from pandas import read_csv + +CORRELATION_THRESHOLD = 0.99 # Less than 0.99 is undesirable +sample_data = read_csv('data/sample.csv', index_col=0, parse_dates=True, infer_datetime_format=False, keep_date_col=True) \ No newline at end of file diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py new file mode 100644 index 0000000..4689331 --- /dev/null +++ b/tests/test_indicator_overlap.py @@ -0,0 +1,227 @@ +from .context import pandas_ta +from .data import sample_data, CORRELATION_THRESHOLD + +from unittest import TestCase, skip +import pandas.util.testing as pdt +from pandas import DataFrame, Series + +import talib as tal + +VERBOSE = False + + +class TestOverlap(TestCase): + @classmethod + def setUpClass(cls): + cls.data = sample_data + cls.open = cls.data['open'] + cls.high = cls.data['high'] + cls.low = cls.data['low'] + cls.close = cls.data['close'] + cls.volume = cls.data['volume'] + cls.correlation_threshold = CORRELATION_THRESHOLD + + @classmethod + def tearDownClass(cls): + del cls.data + del cls.open + del cls.high + del cls.low + del cls.close + del cls.volume + + + def setUp(self): + self.overlap = pandas_ta.overlap + + def tearDown(self): + del self.overlap + + + def test_dema(self): + dema = self.overlap.dema(self.close) + self.assertIsInstance(dema, Series) + self.assertEqual(dema.name, 'DEMA_10') + + try: + tal_dema = tal.DEMA(self.close, 10) + pdt.assert_series_equal(dema, tal_dema, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(dema, tal_dema) + print(f"\nanalysis['corr']: {round(analysis['corr'], 3)}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"dema has low correlation: {analysis['corr']}") + + def test_ema(self): + ema = self.overlap.ema(self.close, presma=False) + self.assertIsInstance(ema, Series) + self.assertEqual(ema.name, 'EMA_10') + + try: + tal_ema = tal.EMA(self.close, 10) + pdt.assert_series_equal(ema, tal_ema, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(ema, tal_ema) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"ema has low correlation: {analysis['corr']}") + + def test_fwma(self): + fwma = self.overlap.fwma(self.close) + self.assertIsInstance(fwma, Series) + self.assertEqual(fwma.name, 'FWMA_10') + + def test_hl2(self): + hl2 = self.overlap.hl2(self.high, self.low) + self.assertIsInstance(hl2, Series) + self.assertEqual(hl2.name, 'HL2') + + def test_hlc3(self): + hlc3 = self.overlap.hlc3(self.high, self.low, self.close) + self.assertIsInstance(hlc3, Series) + self.assertEqual(hlc3.name, 'HLC3') + + try: + tal_typicalprice = tal.TYPPRICE(self.high, self.low, self.close) + pdt.assert_series_equal(hlc3, tal_typicalprice, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(hlc3, tal_typicalprice) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"hlc3/typicalprice has low correlation: {analysis['corr']}") + + def test_hma(self): + hma = self.overlap.hma(self.close) + self.assertIsInstance(hma, Series) + self.assertEqual(hma.name, 'HMA_10') + + # @skip("close index requires Daily dates") + def test_ichimoku(self): + ichimoku = self.overlap.ichimoku(self.high, self.low, self.close) + self.assertIsInstance(ichimoku, DataFrame) + self.assertEqual(ichimoku.name, 'ICHIMOKU_9_26_52') + + def test_midpoint(self): + # talib.MIDPOINT(timeperiod >= 2) + midpoint = self.overlap.midpoint(self.close) + self.assertIsInstance(midpoint, Series) + self.assertEqual(midpoint.name, 'MIDPOINT_2') + + try: + tal_midpoint = tal.MIDPOINT(self.close, 2) + pdt.assert_series_equal(midpoint, tal_midpoint, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(midpoint, tal_midpoint) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"midpoint has low correlation: {analysis['corr']}") + + def test_midprice(self): + # talib.MIDPRICE(timeperiod >= 2) + midprice = self.overlap.midprice(self.high, self.low) + self.assertIsInstance(midprice, Series) + self.assertEqual(midprice.name, 'MIDPRICE_2') + + try: + tal_midprice = tal.MIDPRICE(self.high, self.low, 2) + pdt.assert_series_equal(midprice, tal_midprice, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(midprice, tal_midprice) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"midprice has low correlation: {analysis['corr']}") + + def test_ohlc4(self): + ohlc4 = self.overlap.ohlc4(self.open, self.high, self.low, self.close) + self.assertIsInstance(ohlc4, Series) + self.assertEqual(ohlc4.name, 'OHLC4') + + def test_pwma(self): + pwma = self.overlap.pwma(self.close) + self.assertIsInstance(pwma, Series) + self.assertEqual(pwma.name, 'PWMA_10') + + def test_rma(self): + rma = self.overlap.rma(self.close) + self.assertIsInstance(rma, Series) + self.assertEqual(rma.name, 'RMA_10') + + def test_sma(self): + sma = self.overlap.sma(self.close) + self.assertIsInstance(sma, Series) + self.assertEqual(sma.name, 'SMA_10') + + try: + tal_sma = tal.SMA(self.close, 10) + pdt.assert_series_equal(sma, tal_sma, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(sma, tal_sma) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"sma has low correlation: {analysis['corr']}") + + def test_t3(self): + t3 = self.overlap.t3(self.close) + self.assertIsInstance(t3, Series) + self.assertEqual(t3.name, 'T3_10_0.7') + + try: + tal_t3 = tal.T3(self.close, 10) + pdt.assert_series_equal(t3, tal_t3, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(t3, tal_t3) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"t3 has low correlation: {analysis['corr']}") + + def test_tema(self): + tema = self.overlap.tema(self.close) + self.assertIsInstance(tema, Series) + self.assertEqual(tema.name, 'TEMA_10') + + try: + tal_tema = tal.TEMA(self.close, 10) + pdt.assert_series_equal(tema, tal_tema, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(tema, tal_tema) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"tema has low correlation: {analysis['corr']}") + + def test_trima(self): + trima = self.overlap.trima(self.close) + self.assertIsInstance(trima, Series) + self.assertEqual(trima.name, 'TRIMA_10') + + try: + tal_trima = tal.TRIMA(self.close, 10) + pdt.assert_series_equal(trima, tal_trima, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(trima, tal_trima) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"trima has low correlation: {analysis['corr']}") + + def test_vwap(self): + vwap = self.overlap.vwap(self.high, self.low, self.close, self.volume) + self.assertIsInstance(vwap, Series) + self.assertEqual(vwap.name, 'VWAP') + + def test_vwma(self): + vwma = self.overlap.vwma(self.close, self.volume) + self.assertIsInstance(vwma, Series) + self.assertEqual(vwma.name, 'VWMA_10') + + def test_wma(self): + wma = self.overlap.wma(self.close) + self.assertIsInstance(wma, Series) + self.assertEqual(wma.name, 'WMA_10') + + try: + tal_wma = tal.WMA(self.close, 10) + pdt.assert_series_equal(wma, tal_wma, check_names=False) + except AssertionError as ae: + analysis = pandas_ta.utils.df_error_analysis(wma, tal_wma) + print(f"\nanalysis['corr']: {analysis['corr']}") if VERBOSE else None + if analysis['corr'] < self.correlation_threshold: + raise AssertionError(f"wma has low correlation: {analysis['corr']}") diff --git a/tests/test_indicator_overlap_ext.py b/tests/test_indicator_overlap_ext.py new file mode 100644 index 0000000..225e38e --- /dev/null +++ b/tests/test_indicator_overlap_ext.py @@ -0,0 +1,123 @@ +from .context import pandas_ta +from .data import sample_data + +from unittest import TestCase +from pandas import DataFrame + + + +class TestOverlapExtension(TestCase): + @classmethod + def setUpClass(cls): + cls.data = sample_data + + @classmethod + def tearDownClass(cls): + del cls.data + + + def setUp(self): + pass + + def tearDown(self): + pass + + + def test_dema_ext(self): + self.data.ta.dema(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'DEMA_10') + + def test_ema_ext(self): + self.data.ta.ema(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'EMA_10') + + def test_fwma_ext(self): + self.data.ta.fwma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'FWMA_10') + + def test_hl2_ext(self): + self.data.ta.hl2(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'HL2') + + def test_hlc3_ext(self): + self.data.ta.hlc3(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'HLC3') + + def test_hma_ext(self): + self.data.ta.hma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'HMA_10') + + def test_ichimoku_ext(self): + self.data.ta.ichimoku(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-5], 'ISA_9') + self.assertEqual(self.data.columns[-4], 'ISB_26') + self.assertEqual(self.data.columns[-3], 'ITS_9') + self.assertEqual(self.data.columns[-2], 'IKS_26') + self.assertEqual(self.data.columns[-1], 'ICS_26') + + def test_midpoint_ext(self): + self.data.ta.midpoint(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'MIDPOINT_2') + + def test_midprice_ext(self): + self.data.ta.midprice(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'MIDPRICE_2') + + def test_ohlc4_ext(self): + self.data.ta.ohlc4(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'OHLC4') + + def test_pwma_ext(self): + self.data.ta.pwma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'PWMA_10') + + def test_rma_ext(self): + self.data.ta.rma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'RMA_10') + + def test_sma_ext(self): + self.data.ta.sma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'SMA_10') + + def test_t3_ext(self): + self.data.ta.t3(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'T3_10_0.7') + + def test_tema_ext(self): + self.data.ta.tema(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'TEMA_10') + + def test_trima_ext(self): + self.data.ta.trima(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'TRIMA_10') + + def test_vwap_ext(self): + self.data.ta.vwap(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'VWAP') + + def test_vwma_ext(self): + self.data.ta.vwma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'VWMA_10') + + def test_wma_ext(self): + self.data.ta.wma(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(self.data.columns[-1], 'WMA_10') \ No newline at end of file diff --git a/tests/test_indicator_performance.py b/tests/test_indicator_performance.py index 8ad1c03..c6a4281 100644 --- a/tests/test_indicator_performance.py +++ b/tests/test_indicator_performance.py @@ -1,15 +1,15 @@ +from .context import pandas_ta +from .data import sample_data + from unittest import TestCase -import numpy.testing as npt -import pandas.util.testing as pdt -from pandas import DataFrame, read_csv, Series +from pandas import Series -from pandas_ta import performance class TestPerformace(TestCase): @classmethod def setUpClass(cls): - cls.data = read_csv('data/sample.csv', index_col=0, parse_dates=True, infer_datetime_format=False, keep_date_col=True) + cls.data = sample_data cls.close = cls.data['close'] @classmethod @@ -19,7 +19,7 @@ class TestPerformace(TestCase): def setUp(self): - self.performance = performance + self.performance = pandas_ta.performance def tearDown(self): del self.performance @@ -39,5 +39,4 @@ class TestPerformace(TestCase): self.assertEqual(percent_return.name, 'PCTRET_1') cumpercent_return = self.performance.percent_return(self.close, cumulative=True) - self.assertEqual(cumpercent_return.name, 'CUMPCTRET_1') - + self.assertEqual(cumpercent_return.name, 'CUMPCTRET_1') \ No newline at end of file diff --git a/tests/test_indicator_performance_ext.py b/tests/test_indicator_performance_ext.py index 7c9eeae..ed5cd7e 100644 --- a/tests/test_indicator_performance_ext.py +++ b/tests/test_indicator_performance_ext.py @@ -1,21 +1,21 @@ +from .context import pandas_ta +from .data import sample_data + from unittest import TestCase -import numpy.testing as npt -import pandas.util.testing as pdt -from pandas import DataFrame, read_csv, Series +# import numpy.testing as npt +# import pandas.util.testing as pdt +from pandas import DataFrame#, Series -import pandas_ta as ta class TestPerformaceExtension(TestCase): @classmethod def setUpClass(cls): - cls.data = read_csv('data/sample.csv', index_col=0, parse_dates=True, infer_datetime_format=False, keep_date_col=True) - cls.close = cls.data['close'] + cls.data = sample_data @classmethod def tearDownClass(cls): del cls.data - del cls.close def setUp(self): @@ -41,9 +41,4 @@ class TestPerformaceExtension(TestCase): self.data.ta.percent_return(append=True, cumulative=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'CUMPCTRET_1') - - # Example - # pta_sma = self.data[self.data.columns[-1]] - # tal_sma = tal.SMA(self.close) - # pdt.assert_series_equal(pta_sma, tal_sma) \ No newline at end of file + self.assertEqual(self.data.columns[-1], 'CUMPCTRET_1') \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py index 2ad611c..ed0971c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,4 +1,4 @@ -from pandas_ta import utils as utils +from .context import pandas_ta from unittest import TestCase from unittest.mock import patch @@ -7,9 +7,10 @@ import numpy as np import numpy.testing as npt from pandas import DataFrame + class TestUtilities(TestCase): def setUp(self): - self.utils = utils + self.utils = pandas_ta.utils def tearDown(self): del self.utils