diff --git a/Makefile b/Makefile index 97fdf69..043a64a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ clean: find . -name '*.pyc' -exec rm -f {} + +caches: + find ./pandas_ta | grep -E "(__pycache__|\.pyc|\.pyo$\)" + init: pip install -r requirements.txt diff --git a/README.md b/README.md index a499b9a..f3d94c5 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,12 @@ All the indicators return a named Series or a DataFrame in uppercase underscore Choppiness Index (chop) Chande Kroll Stop (cksp) Entropy (entropy) + Heikin-Ashi Candles (ha) KDJ (kdj) Parabolic Stop and Reverse (psar) Price Distance (pdist) Psycholigical Line (psl) + Supertrend (supertrend) Weighted Closing Price (wcp) ### __Added utilities:__ Above (above) @@ -182,6 +184,10 @@ df.ta.adjusted = None # __Technical Analysis Indicators__ (_by Category_) +## _Candles_ (1) + +* _Heikin-Ashi_: **ha** + ## _Momentum_ (25) * _Awesome Oscillator_: **ao** @@ -215,7 +221,7 @@ df.ta.adjusted = None |:--------:| | ![Example MACD](/images/SPY_MACD.png) | -## _Overlap_ (25) +## _Overlap_ (26) * _Double Exponential Moving Average_: **dema** * _Exponential Moving Average_: **ema** @@ -224,17 +230,18 @@ df.ta.adjusted = None * _High-Low-Close Average_: **hlc3** * Commonly known as 'Typical Price' in Technical Analysis literature * _Hull Exponential Moving Average_: **hma** -* _Kaufman's Adaptive Moving Average_: **kama** * _Ichimoku Kinkō Hyō_: **ichimoku** * Use: help(ta.ichimoku). Returns two DataFrames. +* _Kaufman's Adaptive Moving Average_: **kama** * _Linear Regression_: **linreg** * _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** * _Sine Weighted Moving Average_: **sinwma** +* _Simple Moving Average_: **sma** +* _Supertrend_: **supertrend** * _Symmetric Weighted Moving Average_: **swma** * _T3 Moving Average_: **t3** * _Triple Exponential Moving Average_: **tema** @@ -355,4 +362,4 @@ Use parameter: cumulative=**True** for cumulative results. * 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 +Please leave any comments, feedback, suggestions, or indicator requests. \ No newline at end of file diff --git a/pandas_ta/candles/__init__.py b/pandas_ta/candles/__init__.py new file mode 100644 index 0000000..47fbd83 --- /dev/null +++ b/pandas_ta/candles/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from .ha import ha \ No newline at end of file diff --git a/pandas_ta/candles/ha.py b/pandas_ta/candles/ha.py new file mode 100644 index 0000000..350174d --- /dev/null +++ b/pandas_ta/candles/ha.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +import numpy as np +from pandas import DataFrame +from pandas_ta.utils import get_offset, verify_series + + +def ha(open_, high, low, close, offset=None, **kwargs): + """Indicator: Heikin Ashi""" + # Validate Arguments + open_ = verify_series(open_) + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + offset = get_offset(offset) + + # Calculate Result + m = close.size + df = DataFrame({ + "HA_open": 0.5 * (open_.iloc[0] + close.iloc[0]), + "HA_high": high, + "HA_low": low, + "HA_close": 0.25 * (open_ + high + low + close) + }) + + for i in range(1, m): + df["HA_open"][i] = 0.5 * (df["HA_open"][i - 1] + df["HA_close"][i - 1]) + + df["HA_high"] = df[["HA_open", "HA_high", "HA_close"]].max(axis=1) + df["HA_low"] = df[["HA_open", "HA_low", "HA_close"]].min(axis=1) + + # Offset + if offset != 0: + df = df.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + df.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + df.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + df.name = "Heikin-Ashi" + df.category = "candles" + + return df + + +ha.__doc__ = \ +"""Heikin Ashi (HA) + +The Heikin-Ashi technique averages price data to create a Japanese +candlestick chart that filters out market noise. Heikin-Ashi charts, +developed by Munehisa Homma in the 1700s, share some characteristics +with standard candlestick charts but differ based on the values used +to create each candle. Instead of using the open, high, low, and close +like standard candlestick charts, the Heikin-Ashi technique uses a +modified formula based on two-period averages. This gives the chart a +smoother appearance, making it easier to spots trends and reversals, +but also obscures gaps and some price data. + +Sources: + https://www.investopedia.com/terms/h/heikinashi.asp + +Calculation: + HA_OPEN[0] = (open[0] + close[0]) / 2 + HA_CLOSE = (open[0] + high[0] + low[0] + close[0]) / 4 + + for i > 1 in df.index: + HA_OPEN = (HA_OPEN[i−1] + HA_CLOSE[i−1]) / 2 + + HA_HIGH = MAX(HA_OPEN, HA_HIGH, HA_CLOSE) + HA_LOW = MIN(HA_OPEN, HA_LOW, HA_CLOSE) + + How to Calculate Heikin-Ashi + + Use one period to create the first Heikin-Ashi (HA) candle, using + the formulas. For example use the high, low, open, and close to + create the first HA close price. Use the open and close to create + the first HA open. The high of the period will be the first HA high, + and the low will be the first HA low. With the first HA calculated, + it is now possible to continue computing the HA candles per the formulas. +​​ +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 + +Kwargs: + fillna (value, optional): pd.DataFrame.fillna(value) + fill_method (value, optional): Type of fill method + +Returns: + pd.DataFrame: ha_open, ha_high,ha_low, ha_close columns. +""" diff --git a/pandas_ta/core.py b/pandas_ta/core.py index 9111192..189dc40 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -5,6 +5,7 @@ from functools import wraps import pandas as pd from pandas.core.base import PandasObject +from pandas_ta.candles import * from pandas_ta.momentum import * from pandas_ta.overlap import * from pandas_ta.performance import * @@ -14,7 +15,7 @@ from pandas_ta.volatility import * from pandas_ta.volume import * from pandas_ta.utils import * -version = ".".join(("0", "1", "64b")) +version = ".".join(("0", "1", "65b")) def finalize(method): @wraps(method) @@ -204,6 +205,7 @@ class AnalysisIndicators(BasePandasObject): if result is None: return else: prefix = suffix = "" + # delimiter = kwargs.pop("delimiter", "_") if "prefix" in kwargs: prefix = f"{kwargs['prefix']}_" @@ -367,6 +369,17 @@ class AnalysisIndicators(BasePandasObject): self._all(**kwargs) if name == "all" else None + # Candles + @finalize + def ha(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 = ha(open_=open_, high=high, low=low, close=close, offset=offset, **kwargs) + return result + # Momentum Indicators @finalize def ao(self, high=None, low=None, fast=None, slow=None, offset=None, **kwargs): @@ -534,10 +547,10 @@ class AnalysisIndicators(BasePandasObject): return result @finalize - def trix(self, close=None, length=None, drift=None, offset=None, **kwargs): + def trix(self, close=None, length=None, signal=None, scalar=None, drift=None, offset=None, **kwargs): close = self._get_column(close, 'close') - result = trix(close=close, length=length, drift=drift, offset=offset, **kwargs) + result = trix(close=close, length=length, signal=signal, scalar=scalar, drift=drift, offset=offset, **kwargs) return result @finalize @@ -691,6 +704,15 @@ class AnalysisIndicators(BasePandasObject): result = sma(close=close, length=length, offset=offset, **kwargs) return result + @finalize + def supertrend(self, high=None, low=None, close=None, length=None, multiplier=None, offset=None, **kwargs): + high = self._get_column(high, 'high') + low = self._get_column(low, 'low') + close = self._get_column(close, 'close') + + result = supertrend(high=high, low=low, close=close, length=length, multiplier=multiplier, offset=offset, **kwargs) + return result + @finalize def swma(self, close=None, length=None, offset=None, **kwargs): close = self._get_column(close, 'close') @@ -912,17 +934,6 @@ class AnalysisIndicators(BasePandasObject): result = dpo(close=close, length=length, centered=centered, offset=offset, **kwargs) return result - def ha(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 = ha(open=open, high=high, low=low, close=close, offset=offset, **kwargs) - self._add_prefix_suffix(result, **kwargs) - self._append(result, **kwargs) - return result - @finalize def increasing(self, close=None, length=None, asint=True, offset=None, **kwargs): close = self._get_column(close, 'close') @@ -979,18 +990,15 @@ class AnalysisIndicators(BasePandasObject): self._append(result, **kwargs) return result - def supertrend(self, high=None, low=None, close=None, period=None, multiplier=None, mamode=None, drift=None, - offset=None, **kwargs): + @finalize + def supertrend(self, high=None, low=None, close=None, period=None, multiplier=None, mamode=None, drift=None, offset=None, **kwargs): high = self._get_column(high, 'high') low = self._get_column(low, 'low') close = self._get_column(close, 'close') result = supertrend(high=high, low=low, close=close, period=period, multiplier=multiplier, mamode=mamode, drift=drift, offset=offset, **kwargs) - self._add_prefix_suffix(result, **kwargs) - self._append(result, **kwargs) return result - @finalize def vortex(self, high=None, low=None, close=None, drift=None, offset=None, **kwargs): high = self._get_column(high, 'high') @@ -1115,12 +1123,12 @@ class AnalysisIndicators(BasePandasObject): return result @finalize - def natr(self, high=None, low=None, close=None, length=None, mamode=None, offset=None, **kwargs): + def natr(self, high=None, low=None, close=None, length=None, mamode=None, scalar=None, offset=None, **kwargs): high = self._get_column(high, 'high') low = self._get_column(low, 'low') close = self._get_column(close, 'close') - result = natr(high=high, low=low, close=close, length=length, mamode=mamode, offset=offset, **kwargs) + result = natr(high=high, low=low, close=close, length=length, mamode=mamode, scalar=scalar, offset=offset, **kwargs) return result @finalize diff --git a/pandas_ta/momentum/cmo.py b/pandas_ta/momentum/cmo.py index de72d34..126f7e5 100644 --- a/pandas_ta/momentum/cmo.py +++ b/pandas_ta/momentum/cmo.py @@ -7,6 +7,7 @@ def cmo(close, length=None, scalar=None, drift=None, offset=None, **kwargs): close = verify_series(close) length = int(length) if length and length > 0 else 14 scalar = float(scalar) if scalar else 100 + talib = kwargs.pop("talib", True) drift = get_drift(drift) offset = get_offset(offset) @@ -17,11 +18,15 @@ def cmo(close, length=None, scalar=None, drift=None, offset=None, **kwargs): positive[positive < 0] = 0 # Make negatives 0 for the postive series negative[negative > 0] = 0 # Make postives 0 for the negative series - positive_avg = positive.ewm(com=length, adjust=False).mean() - negative_avg = negative.ewm(com=length, adjust=False).mean().abs() + if talib: + pos_ = positive.ewm(com=length, adjust=False).mean() + neg_ = negative.ewm(com=length, adjust=False).mean().abs() + else: + pos_ = positive.rolling(length).sum() + neg_ = negative.abs().rolling(length).sum() - # Previous steps same as RSI - cmo = scalar * (positive_avg - negative_avg) / (positive_avg + negative_avg) + cmo = scalar * (pos_ - neg_) + cmo /= pos_ + neg_ # Offset if offset != 0: @@ -60,6 +65,7 @@ Calculation: Args: close (pd.Series): Series of 'close's scalar (float): How much to magnify. Default: 100 + talib (bool): If True, uses TA-Libs implementation. Otherwise uses EMA version. Default: True drift (int): The short period. Default: 1 offset (int): How many periods to offset the result. Default: 0 diff --git a/pandas_ta/momentum/kst.py b/pandas_ta/momentum/kst.py index 0112247..211a501 100644 --- a/pandas_ta/momentum/kst.py +++ b/pandas_ta/momentum/kst.py @@ -45,14 +45,14 @@ def kst(close, roc1=None, roc2=None, roc3=None, roc4=None, sma1=None, sma2=None, # Name and Categorize it kst.name = f"KST_{roc1}_{roc2}_{roc3}_{roc4}_{sma1}_{sma2}_{sma3}_{sma4}" - kst_signal.name = f"KSTS_{signal}" - kst.category = kst_signal.category = 'momentum' + kst_signal.name = f"KSTs_{signal}" + kst.category = kst_signal.category = "momentum" # Prepare DataFrame to return data = {kst.name: kst, kst_signal.name: kst_signal} kstdf = DataFrame(data) kstdf.name = f"KST_{roc1}_{roc2}_{roc3}_{roc4}_{sma1}_{sma2}_{sma3}_{sma4}_{signal}" - kstdf.category = 'momentum' + kstdf.category = "momentum" return kstdf diff --git a/pandas_ta/momentum/trix.py b/pandas_ta/momentum/trix.py index 6549903..74c195c 100644 --- a/pandas_ta/momentum/trix.py +++ b/pandas_ta/momentum/trix.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- -from ..overlap.ema import ema -from ..utils import get_drift, get_offset, verify_series +from pandas import DataFrame +from pandas_ta.overlap.ema import ema +from pandas_ta.utils import get_drift, get_offset, verify_series -def trix(close, length=None, drift=None, offset=None, **kwargs): +def trix(close, length=None, signal=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Trix (TRIX)""" # Validate Arguments close = verify_series(close) length = int(length) if length and length > 0 else 30 + signal = int(signal) if signal and signal > 0 else 9 + scalar = float(scalar) if scalar else 100 min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length drift = get_drift(drift) offset = get_offset(offset) @@ -15,17 +18,34 @@ def trix(close, length=None, drift=None, offset=None, **kwargs): ema1 = ema(close=close, length=length, **kwargs) ema2 = ema(close=ema1, length=length, **kwargs) ema3 = ema(close=ema2, length=length, **kwargs) - trix = 100 * ema3.pct_change(drift) + trix = scalar * ema3.pct_change(drift) + + trix_signal = trix.rolling(signal).mean() # Offset if offset != 0: trix = trix.shift(offset) + trix_signal = trix_signal.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + trix.fillna(kwargs['fillna'], inplace=True) + trix_signal.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + trix.fillna(method=kwargs['fill_method'], inplace=True) + trix_signal.fillna(method=kwargs['fill_method'], inplace=True) # Name & Category - trix.name = f"TRIX_{length}" - trix.category = 'momentum' + trix.name = f"TRIX_{length}_{signal}" + trix_signal.name = f"TRIXs_{length}_{signal}" + trix.category = trix_signal.category = "momentum" - return trix + # Prepare DataFrame to return + df = DataFrame({trix.name: trix, trix_signal.name: trix_signal}) + df.name = f"TRIX_{length}_{signal}" + df.category = "momentum" + + return df @@ -50,6 +70,8 @@ Calculation: Args: close (pd.Series): Series of 'close's length (int): It's period. Default: 18 + signal (int): It's period. Default: 9 + 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 diff --git a/pandas_ta/overlap/__init__.py b/pandas_ta/overlap/__init__.py index 4cbc0ef..25750ef 100644 --- a/pandas_ta/overlap/__init__.py +++ b/pandas_ta/overlap/__init__.py @@ -15,6 +15,7 @@ from .pwma import pwma from .rma import rma from .sinwma import sinwma from .sma import sma +from .supertrend import supertrend from .swma import swma from .t3 import t3 from .tema import tema diff --git a/pandas_ta/overlap/hlc3.py b/pandas_ta/overlap/hlc3.py index 4a1abb8..41a9a0e 100644 --- a/pandas_ta/overlap/hlc3.py +++ b/pandas_ta/overlap/hlc3.py @@ -10,7 +10,7 @@ def hlc3(high, low, close, offset=None, **kwargs): offset = get_offset(offset) # Calculate Result - hlc3 = (high + low + close) / 3 + hlc3 = (high + low + close) / 3. # Offset if offset != 0: diff --git a/pandas_ta/overlap/supertrend.py b/pandas_ta/overlap/supertrend.py new file mode 100644 index 0000000..de022ce --- /dev/null +++ b/pandas_ta/overlap/supertrend.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +from numpy import NaN as npNaN +from pandas import DataFrame +from pandas_ta.overlap import hl2 +from pandas_ta.volatility import atr +from pandas_ta.utils import get_offset, verify_series + + +def supertrend(high, low, close, length=None, multiplier=None, offset=None, **kwargs): + """Indicator: Supertrend""" + # Validate Arguments + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + length = int(length) if length and length > 0 else 7 + multiplier = float(multiplier) if multiplier and multiplier > 0 else 3. + offset = get_offset(offset) + + # Calculate Results + m = close.size + dir_, trend = [0] * m, [0] * m + long, short = [npNaN] * m, [npNaN] * m + + hl2_ = hl2(high, low) + matr = multiplier * atr(high, low, close, length) + upperband = hl2_ + matr + lowerband = hl2_ - matr + + for i in range(1, m): + if close.iloc[i] > upperband.iloc[i - 1]: + dir_[i] = 1 + elif close.iloc[i] < lowerband.iloc[i - 1]: + dir_[i] = -1 + else: + dir_[i] = dir_[i - 1] + if dir_[i] > 0 and lowerband.iloc[i] < lowerband.iloc[i - 1]: + lowerband.iloc[i] = lowerband.iloc[i - 1] + if dir_[i] < 0 and upperband.iloc[i] > upperband.iloc[i - 1]: + upperband.iloc[i] = upperband.iloc[i - 1] + + if dir_[i] > 0: + trend[i] = long[i] = lowerband.iloc[i] + else: + trend[i] = short[i] = upperband.iloc[i] + + # Prepare DataFrame to return + _props = f"_{length}_{multiplier}" + df = DataFrame({ + f"SUPERT{_props}": trend, + f"SUPERTd{_props}": dir_, + f"SUPERTl{_props}": long, + f"SUPERTs{_props}": short + }, index=close.index) + + df.name = f"SUPERT{_props}" + df.category = "overlap" + + # Apply offset if needed + if offset != 0: + df = df.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + df.fillna(kwargs['fillna'], inplace=True) + + if 'fill_method' in kwargs: + df.fillna(method=kwargs['fill_method'], inplace=True) + + return df + + +supertrend.__doc__ = \ +"""Supertrend (supertrend) + +Supertrend is an overlap indicator. It is used to help identify trend +direction, setting stop loss, identify support and resistance, and/or +generate buy & sell signals. + +Sources: + http://www.freebsensetips.com/blog/detail/7/What-is-supertrend-indicator-its-calculation + +Calculation: + Default Inputs: + length=7, multiplier=3.0 + + MID = multiplier * ATR + LOWERBAND = HL2 - MID + UPPERBAND = HL2 + MID + + if UPPERBAND[i] < FINAL_UPPERBAND[i-1] and close[i-1] > FINAL_UPPERBAND[i-1]: + FINAL_UPPERBAND[i] = UPPERBAND[i] + else: + FINAL_UPPERBAND[i] = FINAL_UPPERBAND[i-1]) + + if LOWERBAND[i] > FINAL_LOWERBAND[i-1] and close[i-1] < FINAL_LOWERBAND[i-1]: + FINAL_LOWERBAND[i] = LOWERBAND[i] + else: + FINAL_LOWERBAND[i] = FINAL_LOWERBAND[i-1]) + + if close[i] <= FINAL_UPPERBAND[i]: + SUPERTREND[i] = FINAL_UPPERBAND[i] + else: + SUPERTREND[i] = FINAL_LOWERBAND[i] + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + length (int) : length for ATR calculation. Default: 7 + multiplier (float): Coefficient for upper and lower band distance to midrange. Default: 3.0 + 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: SUPERT (trend), SUPERTd (direction), SUPERTl (long), SUPERTs (short) columns. +""" diff --git a/pandas_ta/overlap/vwap.py b/pandas_ta/overlap/vwap.py index 6684e97..3a8e32f 100644 --- a/pandas_ta/overlap/vwap.py +++ b/pandas_ta/overlap/vwap.py @@ -40,6 +40,7 @@ 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/ + https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:vwap_intraday Calculation: tp = typical_price = hlc3(high, low, close) diff --git a/pandas_ta/performance/trend_return.py b/pandas_ta/performance/trend_return.py index 93824a2..3062f47 100644 --- a/pandas_ta/performance/trend_return.py +++ b/pandas_ta/performance/trend_return.py @@ -14,8 +14,8 @@ def trend_return(close, trend, log=True, cumulative=None, offset=None, trend_res # Calculate Result returns = log_return(close, cumulative=False) if log else percent_return(close, cumulative=False) - m = trend.size tsum = 0 + m = trend.size trend = trend.astype(int) returns = (trend * returns).apply(zero) diff --git a/pandas_ta/trend/__init__.py b/pandas_ta/trend/__init__.py index f98b9da..dbdce91 100644 --- a/pandas_ta/trend/__init__.py +++ b/pandas_ta/trend/__init__.py @@ -6,12 +6,10 @@ from .chop import chop from .cksp import cksp from .decreasing import decreasing from .dpo import dpo -from .ha import ha from .increasing import increasing from .linear_decay import linear_decay from .long_run import long_run from .psar import psar from .qstick import qstick from .short_run import short_run -from .supertrend import supertrend from .vortex import vortex \ No newline at end of file diff --git a/pandas_ta/trend/ha.py b/pandas_ta/trend/ha.py deleted file mode 100644 index d72e4ce..0000000 --- a/pandas_ta/trend/ha.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -from pandas import DataFrame -from pandas_ta.utils import get_offset, verify_series - - -def ha(open, high, low, close, offset=None, **kwargs): - # indicator : Heikin Ashi - # Validate Arguments - open_ = verify_series(open) - high = verify_series(high) - low = verify_series(low) - close = verify_series(close) - offset = get_offset(offset) - - # calculate ha_close - ha_close = 0.25 * (open_ + high + low + close) - - # Initialization of the ha_open array - ha_open = np.zeros(shape=(len(close))) - - # ha_open of the first element - ha_open[0] = 0.5 * (open_[0] + close[0]) - - # calculate ha_open. Based on previous ha_open & ha_close - for i in range(1, len(close)): - ha_open[i] = 0.5 * (ha_open[i-1] + ha_close[i-1]) - - # calculation of ha_high & ha_low - ha_high = np.maximum.reduce([high, ha_open, ha_close]) - ha_low = np.minimum.reduce([low, ha_open, ha_close]) - - # Prepare DataFrame to return - data = {'ha_open': ha_open, 'ha_high': ha_high, 'ha_low': ha_low, 'ha_close': ha_close} - hadf = DataFrame(data) - hadf.name = "Heikin-Ashi" - hadf.category = 'trend' - - # Apply offset if needed - if offset != 0: - hadf = hadf.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - hadf.fillna(kwargs['fillna'], inplace=True) - - if 'fill_method' in kwargs: - hadf.fillna(method=kwargs['fill_method'], inplace=True) - - return hadf - - -ha.__doc__ = \ -"""Heikin Ashi (HA) - -The Heikin-Ashi technique averages price data to create a Japanese candlestick chart that filters out market noise. -Heikin-Ashi charts, developed by Munehisa Homma in the 1700s, -share some characteristics with standard candlestick charts but differ based on the values used to create each candle. -Instead of using the open, high, low, and close like standard candlestick charts, -the Heikin-Ashi technique uses a modified formula based on two-period averages. -This gives the chart a smoother appearance, making it easier to spots trends and reversals, -but also obscures gaps and some price data. - -Sources: - https://www.investopedia.com/terms/h/heikinashi.asp - -Calculation: - The Formula for the Heikin-Ashi technique is: - -Heikin-Ashi Close=(Open0+High0+Low0+Close0)/4 -Heikin-Ashi Open=(HA Open−1+HA Close−1)/2 -Heikin-Ashi High=Max (High0,HA Open0,HA Close0) -Heikin-Ashi Low=Min (Low0,HA Open0,HA Close0) -where:Open0 etc.=Values from the current period -Open−1 etc.=Values from the prior period -HA=Heikin-Ashi - - How to Calculate Heikin-Ashi - - Use one period to create the first Heikin-Ashi (HA) candle, using the formulas. - For example use the high, low, open, and close to create the first HA close price. - Use the open and close to create the first HA open. - The high of the period will be the first HA high, and the low will be the first HA low. - With the first HA calculated, it is now possible to continue computing the HA candles per the formulas. -​​ -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 - - -Kwargs: - fillna (value, optional): pd.DataFrame.fillna(value) - fill_method (value, optional): Type of fill method - -Returns: - pd.DataFrame: ha_open, ha_high,ha_low, ha_close columns. -""" diff --git a/pandas_ta/trend/supertrend.py b/pandas_ta/trend/supertrend.py deleted file mode 100644 index bf5ccee..0000000 --- a/pandas_ta/trend/supertrend.py +++ /dev/null @@ -1,104 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -from pandas import DataFrame -from ..utils import get_offset, verify_series -from ..volatility import atr - - -def supertrend(high, low, close, length=None, multiplier=None, mamode=None, drift=None, offset=None, **kwargs): - # indicator : supertrend - # Validate Arguments - high = verify_series(high) - low = verify_series(low) - close = verify_series(close) - offset = get_offset(offset) - length = int(length) if length and length > 0 else 10 - multiplier = float(multiplier) if multiplier and multiplier > 0 else 3 - min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs[ - 'min_periods'] is not None else length - - supertrend_dir = np.zeros(shape=(len(close))) - strend = np.zeros(shape=(len(close))) - - # Bands initial calculation - midrange = 0.5 * (high + low) - distance = multiplier * atr(high, low, close, length, mamode, drift, offset, min_periods=min_periods) - lowerband = midrange - distance - upperband = midrange + distance - - # final calculation loop - for i in range(1, len(close)): - if close[i] > upperband[i - 1]: - supertrend_dir[i] = 1 - elif close[i] < lowerband[i - 1]: - supertrend_dir[i] = -1 - else: - supertrend_dir[i] = supertrend_dir[i - 1] - if supertrend_dir[i] > 0 and lowerband[i] < lowerband[i - 1]: - lowerband[i] = lowerband[i - 1] - if supertrend_dir[i] < 0 and upperband[i] > upperband[i - 1]: - upperband[i] = upperband[i - 1] - if supertrend_dir[i] < 0: - strend[i] = upperband[i] - else: - strend[i] = lowerband[i] - - # Prepare DataFrame to return - data = {f"supertrend_{length}_{multiplier}": strend, f"supertrend_dir_{length}_{multiplier}": supertrend_dir} - supertrend_df = DataFrame(data) - supertrend_df.name = f"supertrend_{length}_{multiplier}" - supertrend_df.category = 'trend' - - # Apply offset if needed - if offset != 0: - supertrend_df = supertrend_df.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - supertrend_df.fillna(kwargs['fillna'], inplace=True) - - if 'fill_method' in kwargs: - supertrend_df.fillna(method=kwargs['fill_method'], inplace=True) - - return supertrend_df - - -supertrend.__doc__ = \ - """Supertrend (supertrend) - -Supertrend is a trend indicator. It is usually used to help identify trend direction, setting stop loss, -identify support and resistance, and / or generate buy & sell signals. -Calculation is in 2 steps : first a multiple of ATR is added and substracted to the middle of the high - low range. -This gives the upperband and lowerband. -The direction of the trend is then calculated : if close > previous upperband or < previous lowerband, -then trend direction is changed, else it is the same as previous value. -If trend direction is unchanged and down, upperband is set to minimum between current and previous value -If trend direction is unchanged and up, lowerband is set to maximum between current and previous value. - -The final band is then choosen according to the direction of the trend : upperband if trend is downward, -lowerband if trend is upward. -Returned values are : float for final band level, int (1 : upward trend, -1 : downward trend) for trend direction - -Calculation: - Default Inputs: - length = 10 - multiplier = 3 - -Args: - high (pd.Series): Series of 'high's - low (pd.Series): Series of 'low's - close (pd.Series): Series of 'close's - length (int) : length for ATR calculation. Default : 10 - multiplier : coefficient for upper and lower band distance to midrange. Default : 3 - mamode: parameter used for ATR calculation. See ATR documentation. Default : None (= ema) - drift : parameter used for ATR calculation. See ATR documentation. Default : None (= 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 - min_periods (int, optional) : parameter used for ATR calculation. See ATR documentation. Default : length - -Returns: - pd.DataFrame: supertrend (float), supertrend_dir (int) columns. -""" diff --git a/pandas_ta/volatility/natr.py b/pandas_ta/volatility/natr.py index e8b539d..f228801 100644 --- a/pandas_ta/volatility/natr.py +++ b/pandas_ta/volatility/natr.py @@ -2,7 +2,7 @@ from .atr import atr from ..utils import get_drift, get_offset, verify_series -def natr(high, low, close, length=None, mamode=None, drift=None, offset=None, **kwargs): +def natr(high, low, close, length=None, mamode=None, scalar=None, drift=None, offset=None, **kwargs): """Indicator: Normalized Average True Range (NATR)""" # Validate arguments high = verify_series(high) @@ -10,11 +10,13 @@ def natr(high, low, close, length=None, mamode=None, drift=None, offset=None, ** close = verify_series(close) length = int(length) if length and length > 0 else 14 mamode = mamode.lower() if mamode else 'ema' + scalar = float(scalar) if scalar else 100 drift = get_drift(drift) offset = get_offset(offset) # Calculate Result - natr = (100 / close) * atr(high=high, low=low, close=close, length=length, mamode=mamode, drift=drift, offset=offset, **kwargs) + natr = scalar / close + natr *= atr(high=high, low=low, close=close, length=length, mamode=mamode, drift=drift, offset=offset, **kwargs) # Offset if offset != 0: @@ -54,6 +56,7 @@ Args: low (pd.Series): Series of 'low's close (pd.Series): Series of 'close's length (int): The short period. Default: 20 + scalar (float): How much to magnify. Default: 100 offset (int): How many periods to offset the result. Default: 0 Kwargs: diff --git a/pandas_ta/volume/cmf.py b/pandas_ta/volume/cmf.py index e861bcd..03ad4cd 100644 --- a/pandas_ta/volume/cmf.py +++ b/pandas_ta/volume/cmf.py @@ -20,9 +20,9 @@ def cmf(high, low, close, volume, open_=None, length=None, offset=None, **kwargs else: ad = 2 * close - (high + low) # AD with High, Low, Close - 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() + ad *= volume / high_low_range + cmf = ad.rolling(length, min_periods=min_periods).sum() + cmf /= volume.rolling(length, min_periods=min_periods).sum() # Offset if offset != 0: diff --git a/pandas_ta/volume/eom.py b/pandas_ta/volume/eom.py index 0753283..72f83de 100644 --- a/pandas_ta/volume/eom.py +++ b/pandas_ta/volume/eom.py @@ -18,7 +18,8 @@ def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset= # Calculate Result distance = hl2(high=high, low=low) - hl2(high=high.shift(drift), low=low.shift(drift)) - box_ratio = (volume / divisor) / high_low_range + box_ratio = volume / divisor + box_ratio /= high_low_range eom = distance / box_ratio eom = eom.rolling(length, min_periods=min_periods).mean() diff --git a/tests/test_indicator_candle.py b/tests/test_indicator_candle.py new file mode 100644 index 0000000..f41647a --- /dev/null +++ b/tests/test_indicator_candle.py @@ -0,0 +1,40 @@ +from .config import error_analysis, sample_data, CORRELATION, CORRELATION_THRESHOLD, VERBOSE +from .context import pandas_ta + +from unittest import TestCase, skip +import pandas.testing as pdt +from pandas import DataFrame, Series + +import talib as tal + + + +class TestCandle(TestCase): + @classmethod + def setUpClass(cls): + cls.data = sample_data + cls.data.columns = cls.data.columns.str.lower() + cls.open = cls.data['open'] + cls.high = cls.data['high'] + cls.low = cls.data['low'] + cls.close = cls.data['close'] + if 'volume' in cls.data.columns: cls.volume = cls.data['volume'] + + @classmethod + def tearDownClass(cls): + del cls.open + del cls.high + del cls.low + del cls.close + if hasattr(cls, 'volume'): del cls.volume + del cls.data + + + def setUp(self): pass + def tearDown(self): pass + + + def test_ha(self): + result = pandas_ta.ha(self.open, self.high, self.low, self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, "Heikin-Ashi") \ No newline at end of file diff --git a/tests/test_indicator_candle_ext.py b/tests/test_indicator_candle_ext.py new file mode 100644 index 0000000..9487505 --- /dev/null +++ b/tests/test_indicator_candle_ext.py @@ -0,0 +1,29 @@ +from .config import sample_data +from .context import pandas_ta + +from unittest import TestCase +from pandas import DataFrame + + + +class TestCandleExtension(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_ha_ext(self): + self.data.ta.ha(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(list(self.data.columns[-4:]), ['HA_open', 'HA_high', 'HA_low', 'HA_close']) \ No newline at end of file diff --git a/tests/test_indicator_momentum.py b/tests/test_indicator_momentum.py index b27fb26..04dc026 100644 --- a/tests/test_indicator_momentum.py +++ b/tests/test_indicator_momentum.py @@ -314,8 +314,8 @@ class TestMomentum(TestCase): def test_trix(self): result = pandas_ta.trix(self.close) - self.assertIsInstance(result, Series) - self.assertEqual(result.name, 'TRIX_30') + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, 'TRIX_30_9') def test_tsi(self): result = pandas_ta.tsi(self.close) diff --git a/tests/test_indicator_momentum_ext.py b/tests/test_indicator_momentum_ext.py index d5641ef..637fb08 100644 --- a/tests/test_indicator_momentum_ext.py +++ b/tests/test_indicator_momentum_ext.py @@ -81,7 +81,7 @@ class TestMomentumExtension(TestCase): def test_kst_ext(self): self.data.ta.kst(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(list(self.data.columns[-2:]), ['KST_10_15_20_30_10_10_10_15', 'KSTS_9']) + self.assertEqual(list(self.data.columns[-2:]), ['KST_10_15_20_30_10_10_10_15', 'KSTs_9']) def test_macd_ext(self): self.data.ta.macd(append=True) @@ -139,7 +139,7 @@ class TestMomentumExtension(TestCase): def test_trix_ext(self): self.data.ta.trix(append=True) self.assertIsInstance(self.data, DataFrame) - self.assertEqual(self.data.columns[-1], 'TRIX_30') + self.assertEqual(list(self.data.columns[-2:]), ['TRIX_30_9', 'TRIXs_30_9']) def test_tsi_ext(self): self.data.ta.tsi(append=True) diff --git a/tests/test_indicator_overlap.py b/tests/test_indicator_overlap.py index 30890ac..401c816 100644 --- a/tests/test_indicator_overlap.py +++ b/tests/test_indicator_overlap.py @@ -243,6 +243,11 @@ class TestOverlap(TestCase): self.assertIsInstance(result, Series) self.assertEqual(result.name, 'SWMA_10') + def test_supertrend(self): + result = pandas_ta.supertrend(self.high, self.low, self.close) + self.assertIsInstance(result, DataFrame) + self.assertEqual(result.name, 'SUPERT_7_3.0') + def test_t3(self): result = pandas_ta.t3(self.close) self.assertIsInstance(result, Series) diff --git a/tests/test_indicator_overlap_ext.py b/tests/test_indicator_overlap_ext.py index 6015116..47ee84e 100644 --- a/tests/test_indicator_overlap_ext.py +++ b/tests/test_indicator_overlap_ext.py @@ -108,6 +108,11 @@ class TestOverlapExtension(TestCase): self.assertIsInstance(self.data, DataFrame) self.assertEqual(self.data.columns[-1], 'SWMA_10') + def test_supertrend_ext(self): + self.data.ta.supertrend(append=True) + self.assertIsInstance(self.data, DataFrame) + self.assertEqual(list(self.data.columns[-4:]), ["SUPERT_7_3.0", "SUPERTd_7_3.0", "SUPERTl_7_3.0", "SUPERTs_7_3.0"]) + def test_t3_ext(self): self.data.ta.t3(append=True) self.assertIsInstance(self.data, DataFrame)