From 38ea426eb448a00684587aa1c60ffdbca1a006b3 Mon Sep 17 00:00:00 2001 From: Kevin Johnson Date: Mon, 20 May 2019 09:08:51 -0700 Subject: [PATCH] trend refactoring --- .gitignore | 4 +- pandas_ta/__init__.py | 12 + pandas_ta/core.py | 11 +- pandas_ta/trend.py | 683 ---------------------------------- pandas_ta/trend/__init__.py | 1 + pandas_ta/trend/adx.py | 141 +++++++ pandas_ta/trend/amat.py | 65 ++++ pandas_ta/trend/aroon.py | 88 +++++ pandas_ta/trend/decreasing.py | 59 +++ pandas_ta/trend/dpo.py | 67 ++++ pandas_ta/trend/increasing.py | 59 +++ pandas_ta/trend/long_run.py | 33 ++ pandas_ta/trend/qstick.py | 69 ++++ pandas_ta/trend/short_run.py | 33 ++ pandas_ta/trend/vortex.py | 91 +++++ pandas_ta/volume/aobv.py | 3 +- setup.py | 2 +- tests/test_indicator_trend.py | 27 +- 18 files changed, 744 insertions(+), 704 deletions(-) delete mode 100644 pandas_ta/trend.py create mode 100644 pandas_ta/trend/__init__.py create mode 100644 pandas_ta/trend/adx.py create mode 100644 pandas_ta/trend/amat.py create mode 100644 pandas_ta/trend/aroon.py create mode 100644 pandas_ta/trend/decreasing.py create mode 100644 pandas_ta/trend/dpo.py create mode 100644 pandas_ta/trend/increasing.py create mode 100644 pandas_ta/trend/long_run.py create mode 100644 pandas_ta/trend/qstick.py create mode 100644 pandas_ta/trend/short_run.py create mode 100644 pandas_ta/trend/vortex.py diff --git a/.gitignore b/.gitignore index 66be022..d935376 100644 --- a/.gitignore +++ b/.gitignore @@ -134,8 +134,6 @@ pandas_pips reqs.txt requirements.txt qd.py -_performance.py +_trend.py _statistics.py -_volatility.py -_volume.py simple.ipynb \ No newline at end of file diff --git a/pandas_ta/__init__.py b/pandas_ta/__init__.py index 323db99..dab2501 100644 --- a/pandas_ta/__init__.py +++ b/pandas_ta/__init__.py @@ -33,6 +33,18 @@ from .statistics.stdev import stdev from .statistics.variance import variance from .statistics.zscore import zscore +# Trend +from .trend.adx import adx +from .trend.amat import amat +from .trend.aroon import aroon +from .trend.decreasing import decreasing +from .trend.dpo import dpo +from .trend.increasing import increasing +from .trend.long_run import long_run +from .trend.qstick import qstick +from .trend.short_run import short_run +from .trend.vortex import vortex + # Volatility from .volatility.accbands import accbands from .volatility.atr import atr diff --git a/pandas_ta/core.py b/pandas_ta/core.py index fc47450..a2118ab 100644 --- a/pandas_ta/core.py +++ b/pandas_ta/core.py @@ -5,7 +5,6 @@ from pandas.core.base import PandasObject from .momentum import * from .overlap import * -from .trend import * from .utils import * class BasePandasObject(PandasObject): @@ -584,36 +583,42 @@ class AnalysisIndicators(BasePandasObject): high = self._get_column(high, 'high') low = self._get_column(low, 'low') close = self._get_column(close, 'close') + from .trend.adx import adx result = adx(high=high, low=low, close=close, drift=drift, offset=offset, **kwargs) self._append(result, **kwargs) return result def amat(self, close=None, fast=None, slow=None, mamode=None, lookback=None, offset=None, **kwargs): close = self._get_column(close, 'close') + from .trend.amat import amat result = amat(close=close, fast=fast, slow=slow, mamode=mamode, lookback=lookback, offset=offset, **kwargs) self._append(result, **kwargs) return result def aroon(self, close=None, length=None, offset=None, **kwargs): close = self._get_column(close, 'close') + from .trend.aroon import aroon result = aroon(close=close, length=length, offset=offset, **kwargs) self._append(result, **kwargs) return result def decreasing(self, close=None, length=None, asint=True, offset=None, **kwargs): close = self._get_column(close, 'close') + from .trend.decreasing import decreasing result = decreasing(close=close, length=length, asint=asint, offset=offset, **kwargs) self._append(result, **kwargs) return result def dpo(self, close=None, length=None, centered=True, offset=None, **kwargs): close = self._get_column(close, 'close') + from .trend.dpo import dpo result = dpo(close=close, length=length, centered=centered, offset=offset, **kwargs) self._append(result, **kwargs) return result def increasing(self, close=None, length=None, asint=True, offset=None, **kwargs): close = self._get_column(close, 'close') + from .trend.increasing import increasing result = increasing(close=close, length=length, asint=asint, offset=offset, **kwargs) self._append(result, **kwargs) return result @@ -623,6 +628,7 @@ class AnalysisIndicators(BasePandasObject): else: fast = self._get_column(fast, f"{fast}") slow = self._get_column(slow, f"{slow}") + from .trend.long_run import long_run result = long_run(fast=fast, slow=slow, length=length, offset=offset, **kwargs) self._append(result, **kwargs) return result @@ -630,6 +636,7 @@ class AnalysisIndicators(BasePandasObject): def qstick(self, open_=None, close=None, length=None, offset=None, **kwargs): open_ = self._get_column(open_, 'open') close = self._get_column(close, 'close') + from .trend.qstick import qstick result = qstick(open_=open_, close=close, length=length, offset=offset, **kwargs) self._append(result, **kwargs) return result @@ -639,6 +646,7 @@ class AnalysisIndicators(BasePandasObject): else: fast = self._get_column(fast, f"{fast}") slow = self._get_column(slow, f"{slow}") + from .trend.short_run import short_run result = short_run(fast=fast, slow=slow, length=length, offset=offset, **kwargs) self._append(result, **kwargs) return result @@ -647,6 +655,7 @@ class AnalysisIndicators(BasePandasObject): high = self._get_column(high, 'high') low = self._get_column(low, 'low') close = self._get_column(close, 'close') + from .trend.vortex import vortex result = vortex(high=high, low=low, close=close, drift=drift, offset=offset, **kwargs) self._append(result, **kwargs) return result diff --git a/pandas_ta/trend.py b/pandas_ta/trend.py deleted file mode 100644 index 1d9f494..0000000 --- a/pandas_ta/trend.py +++ /dev/null @@ -1,683 +0,0 @@ -# -*- coding: utf-8 -*- -import numpy as np -import pandas as pd - -from .momentum import roc -from .overlap import dema, ema, hma, midprice, rma, sma -from .utils import get_drift, get_offset, verify_series, zero -from .volatility.true_range import true_range -from .volatility.atr import atr - - - -def adx(high, low, close, length=None, drift=None, offset=None, **kwargs): - """Indicator: ADX""" - # Validate Arguments - high = verify_series(high) - low = verify_series(low) - close = verify_series(close) - length = length if length and length > 0 else 14 - drift = get_drift(drift) - offset = get_offset(offset) - - # Calculate Result - _atr = atr(high=high, low=low, close=close, length=length) - - up = high - high.shift(drift) - dn = low.shift(drift) - low - - pos = ((up > dn) & (up > 0)) * up - neg = ((dn > up) & (dn > 0)) * dn - - pos = pos.apply(zero) - neg = neg.apply(zero) - - dmp = (100 / _atr) * rma(close=pos, length=length) - dmn = (100 / _atr) * rma(close=neg, length=length) - - dx = 100 * (dmp - dmn).abs() / (dmp + dmn) - adx = rma(close=dx, length=length) - - # Offset - if offset != 0: - dmp = dmp.shift(offset) - dmn = dmn.shift(offset) - adx = adx.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - adx.fillna(kwargs['fillna'], inplace=True) - dmp.fillna(kwargs['fillna'], inplace=True) - dmn.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - adx.fillna(method=kwargs['fill_method'], inplace=True) - dmp.fillna(method=kwargs['fill_method'], inplace=True) - dmn.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - adx.name = f"ADX_{length}" - dmp.name = f"DMP_{length}" - dmn.name = f"DMN_{length}" - - adx.category = dmp.category = dmn.category = 'trend' - - # Prepare DataFrame to return - data = {adx.name: adx, dmp.name: dmp, dmn.name: dmn} - adxdf = pd.DataFrame(data) - adxdf.name = f"ADX_{length}" - adxdf.category = 'trend' - - return adxdf - - -def amat(close=None, fast=None, slow=None, mamode=None, lookback=None, offset=None, **kwargs): - """Indicator: Archer Moving Averages Trends (AMAT)""" - # Validate Arguments - close = verify_series(close) - fast = int(fast) if fast and fast > 0 else 8 - slow = int(slow) if slow and slow > 0 else 21 - lookback = int(lookback) if lookback and lookback > 0 else 2 - mamode = mamode.upper() if mamode else 'EMA' - offset = get_offset(offset) - - # Calculate Result - if mamode == 'EMA': - fast_ma = ema(close=close, length=fast, **kwargs) - slow_ma = ema(close=close, length=slow, **kwargs) - elif mamode == 'HMA': - fast_ma = hma(close=close, length=fast, **kwargs) - slow_ma = hma(close=close, length=slow, **kwargs) - elif mamode == 'LINREG': - fast_ma = linreg(close=close, length=fast, **kwargs) - slow_ma = linreg(close=close, length=slow, **kwargs) - elif mamode == 'RMA': - fast_ma = rma(close=close, length=fast, **kwargs) - slow_ma = rma(close=close, length=slow, **kwargs) - elif mamode == 'SMA': - fast_ma = sma(close=close, length=fast, **kwargs) - slow_ma = sma(close=close, length=slow, **kwargs) - elif mamode == 'WMA': - fast_ma = wma(close=close, length=fast, **kwargs) - slow_ma = wma(close=close, length=slow, **kwargs) - - mas_long = long_run(fast_ma, slow_ma, length=lookback) - mas_short = short_run(fast_ma, slow_ma, length=lookback) - - # Offset - if offset != 0: - mas_long = mas_long.shift(offset) - mas_short = mas_short.shift(offset) - - # # Handle fills - if 'fillna' in kwargs: - mas_long.fillna(kwargs['fillna'], inplace=True) - mas_short.fillna(kwargs['fillna'], inplace=True) - - if 'fill_method' in kwargs: - mas_long.fillna(method=kwargs['fill_method'], inplace=True) - mas_short.fillna(method=kwargs['fill_method'], inplace=True) - - # Prepare DataFrame to return - amatdf = pd.DataFrame({ - f"AMAT_{mas_long.name}": mas_long, - f"AMAT_{mas_short.name}": mas_short - }) - - # Name and Categorize it - amatdf.name = f"AMAT_{mamode}_{fast}_{slow}_{lookback}" - amatdf.category = 'trend' - - return amatdf - - -def aroon(close, length=None, offset=None, **kwargs): - """Indicator: Aroon Oscillator""" - # Validate Arguments - close = verify_series(close) - length = length if length and length > 0 else 14 - min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length - offset = get_offset(offset) - - # Calculate Result - def maxidx(x): - return 100 * (int(np.argmax(x)) + 1) / length - - def minidx(x): - return 100 * (int(np.argmin(x)) + 1) / length - - _close = close.rolling(length, min_periods=min_periods) - aroon_up = _close.apply(maxidx, raw=True) - aroon_down = _close.apply(minidx, raw=True) - - # Handle fills - if 'fillna' in kwargs: - aroon_up.fillna(kwargs['fillna'], inplace=True) - aroon_down.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - aroon_up.fillna(method=kwargs['fill_method'], inplace=True) - aroon_down.fillna(method=kwargs['fill_method'], inplace=True) - - # Offset - if offset != 0: - aroon_up = aroon_up.shift(offset) - aroon_down = aroon_down.shift(offset) - - # Name and Categorize it - aroon_up.name = f"AROONU_{length}" - aroon_down.name = f"AROOND_{length}" - - aroon_down.category = aroon_up.category = 'trend' - - # Prepare DataFrame to return - data = {aroon_down.name: aroon_down, aroon_up.name: aroon_up} - aroondf = pd.DataFrame(data) - aroondf.name = f"AROON_{length}" - aroondf.category = 'trend' - - return aroondf - - -def decreasing(close, length=None, asint=True, offset=None, **kwargs): - """Indicator: Decreasing""" - # Validate Arguments - close = verify_series(close) - length = int(length) if length and length > 0 else 1 - offset = get_offset(offset) - - # Calculate Result - decreasing = close.diff(length) < 0 - if asint: - decreasing = decreasing.astype(int) - - # Offset - if offset != 0: - decreasing = decreasing.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - decreasing.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - decreasing.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - decreasing.name = f"DEC_{length}" - decreasing.category = 'trend' - - return decreasing - - -def dpo(close, length=None, centered=True, offset=None, **kwargs): - """Indicator: Detrend Price Oscillator (DPO)""" - # Validate Arguments - close = verify_series(close) - length = int(length) if length and length > 0 else 1 - 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 - drift = int(0.5 * length) + 1 # int((0.5 * length) + 1) - dpo = close.shift(drift) - close.rolling(length, min_periods=min_periods).mean() - if centered: - dpo = dpo.shift(-drift) - - # Offset - if offset != 0: - dpo = dpo.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - dpo.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - dpo.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - dpo.name = f"DPO_{length}" - dpo.category = 'trend' - - return dpo - - -def increasing(close, length=None, asint=True, offset=None, **kwargs): - """Indicator: Increasing""" - # Validate Arguments - close = verify_series(close) - length = int(length) if length and length > 0 else 1 - offset = get_offset(offset) - - # Calculate Result - increasing = close.diff(length) > 0 - if asint: - increasing = increasing.astype(int) - - # Offset - if offset != 0: - increasing = increasing.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - increasing.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - increasing.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - increasing.name = f"INC_{length}" - increasing.category = 'trend' - - return increasing - - -def long_run(fast, slow, length=None, offset=None, **kwargs): - """Indicator: Long Run""" - # Validate Arguments - fast = verify_series(fast) - slow = verify_series(slow) - length = int(length) if length and length > 0 else 2 - offset = get_offset(offset) - - # Calculate Result - pb = increasing(fast, length) & decreasing(slow, length) # potential bottom or bottom - bi = increasing(fast, length) & increasing(slow, length) # fast and slow are increasing - long_run = pb | bi - - # Offset - if offset != 0: - long_run = long_run.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - long_run.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - long_run.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - long_run.name = f"LR_{length}" - long_run.category = 'trend' - - return long_run - - -def qstick(open_, close, length=None, offset=None, **kwargs): - """Indicator: Q Stick""" - # Validate Arguments - open_ = verify_series(open_) - close = verify_series(close) - length = int(length) if length and length > 0 else 10 - offset = get_offset(offset) - ma = kwargs.pop('ma', 'sma') if 'ma' in kwargs else 'sma' - - # Calculate Result - diff = close - open_ - - if ma in [None, 'sma']: qstick = sma(diff, length=length) - if ma == 'dema': qstick = dema(diff, length=length, **kwargs) - if ma == 'ema': qstick = ema(diff, length=length, **kwargs) - if ma == 'hma': qstick = hma(diff, length=length) - if ma == 'rma': qstick = rma(diff, length=length) - - # Offset - if offset != 0: - qstick = qstick.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - qstick.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - qstick.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - qstick.name = f"QS_{length}" - qstick.category = 'trend' - - return qstick - - -def short_run(fast, slow, length=None, offset=None, **kwargs): - """Indicator: Short Run""" - # Validate Arguments - fast = verify_series(fast) - slow = verify_series(slow) - length = int(length) if length and length > 0 else 2 - offset = get_offset(offset) - - # Calculate Result - pt = decreasing(fast, length) & increasing(slow, length) # potential top or top - bd = decreasing(fast, length) & decreasing(slow, length) # fast and slow are decreasing - short_run = pt | bd - - # Offset - if offset != 0: - short_run = short_run.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - short_run.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - short_run.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - short_run.name = f"SR_{length}" - short_run.category = 'trend' - - return short_run - - -def vortex(high, low, close, length=None, drift=None, offset=None, **kwargs): - """Indicator: Vortex""" - # Validate arguments - high = verify_series(high) - low = verify_series(low) - close = verify_series(close) - length = length if length and length > 0 else 14 - min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length - drift = get_drift(drift) - offset = get_offset(offset) - - # Calculate Result - tr = true_range(high=high, low=low, close=close) - tr_sum = tr.rolling(length, min_periods=min_periods).sum() - - vmp = (high - low.shift(drift)).abs() - vmm = (low - high.shift(drift)).abs() - - vip = vmp.rolling(length, min_periods=min_periods).sum() / tr_sum - vim = vmm.rolling(length, min_periods=min_periods).sum() / tr_sum - - # Offset - if offset != 0: - vip = vip.shift(offset) - vim = vim.shift(offset) - - # Handle fills - if 'fillna' in kwargs: - vip.fillna(kwargs['fillna'], inplace=True) - vim.fillna(kwargs['fillna'], inplace=True) - if 'fill_method' in kwargs: - vip.fillna(method=kwargs['fill_method'], inplace=True) - vim.fillna(method=kwargs['fill_method'], inplace=True) - - # Name and Categorize it - vip.name = f"VTXP_{length}" - vim.name = f"VTXM_{length}" - vip.category = vim.category = 'trend' - - # Prepare DataFrame to return - data = {vip.name: vip, vim.name: vim} - vtxdf = pd.DataFrame(data) - vtxdf.name = f"VTX_{length}" - vtxdf.category = 'trend' - - return vtxdf - - - -# Trend Documentation -adx.__doc__ = \ -"""Average Directional Movement (ADX) - -Average Directional Movement is meant to quantify trend strength by measuring -the amount of movement in a single direction. - -Sources: - https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/average-directional-movement-adx/ - -Calculation: - DMI ADX TREND 2.0 by @TraderR0BERT, NETWORTHIE.COM - //Created by @TraderR0BERT, NETWORTHIE.COM, last updated 01/26/2016 - //DMI Indicator - //Resolution input option for higher/lower time frames - study(title="DMI ADX TREND 2.0", shorttitle="ADX TREND 2.0") - - adxlen = input(14, title="ADX Smoothing") - dilen = input(14, title="DI Length") - thold = input(20, title="Threshold") - - threshold = thold - - //Script for Indicator - dirmov(len) => - up = change(high) - down = -change(low) - truerange = rma(tr, len) - plus = fixnan(100 * rma(up > down and up > 0 ? up : 0, len) / truerange) - minus = fixnan(100 * rma(down > up and down > 0 ? down : 0, len) / truerange) - [plus, minus] - - adx(dilen, adxlen) => - [plus, minus] = dirmov(dilen) - sum = plus + minus - adx = 100 * rma(abs(plus - minus) / (sum == 0 ? 1 : sum), adxlen) - [adx, plus, minus] - - [sig, up, down] = adx(dilen, adxlen) - osob=input(40,title="Exhaustion Level for ADX, default = 40") - col = sig >= sig[1] ? green : sig <= sig[1] ? red : gray - - //Plot Definitions Current Timeframe - p1 = plot(sig, color=col, linewidth = 3, title="ADX") - p2 = plot(sig, color=col, style=circles, linewidth=3, title="ADX") - p3 = plot(up, color=blue, linewidth = 3, title="+DI") - p4 = plot(up, color=blue, style=circles, linewidth=3, title="+DI") - p5 = plot(down, color=fuchsia, linewidth = 3, title="-DI") - p6 = plot(down, color=fuchsia, style=circles, linewidth=3, title="-DI") - h1 = plot(threshold, color=black, linewidth =3, title="Threshold") - - trender = (sig >= up or sig >= down) ? 1 : 0 - bgcolor(trender>0?black:gray, transp=85) - - //Alert Function for ADX crossing Threshold - Up_Cross = crossover(up, threshold) - alertcondition(Up_Cross, title="DMI+ cross", message="DMI+ Crossing Threshold") - Down_Cross = crossover(down, threshold) - alertcondition(Down_Cross, title="DMI- cross", message="DMI- Crossing Threshold") - -Args: - high (pd.Series): Series of 'high's - low (pd.Series): Series of 'low's - close (pd.Series): Series of 'close's - length (int): It's period. Default: 14 - drift (int): The difference 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.DataFrame: adx, dmp, dmn columns. -""" - - -aroon.__doc__ = \ -"""Aroon (AROON) - -Aroon attempts to identify if a security is trending and how strong. - -Sources: - https://www.tradingview.com/wiki/Aroon - https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/aroon-ar/ - -Calculation: - Default Inputs: - length=1 - def maxidx(x): - return 100 * (int(np.argmax(x)) + 1) / length - - def minidx(x): - return 100 * (int(np.argmin(x)) + 1) / length - - _close = close.rolling(length, min_periods=min_periods) - aroon_up = _close.apply(maxidx, raw=True) - aroon_down = _close.apply(minidx, raw=True) - -Args: - close (pd.Series): Series of 'close'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.DataFrame: aroon_up, aroon_down columns. -""" - - -decreasing.__doc__ = \ -"""Decreasing - -Returns True or False if the series is decreasing over a periods. By default, -it returns True and False as 1 and 0 respectively with kwarg 'asint'. - -Sources: - -Calculation: - decreasing = close.diff(length) < 0 - if asint: - decreasing = decreasing.astype(int) - -Args: - close (pd.Series): Series of 'close's - length (int): It's period. Default: 1 - asint (bool): Returns as binary. 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. -""" - - -dpo.__doc__ = \ -"""Detrend Price Oscillator (DPO) - -Is an indicator designed to remove trend from price and make it easier to -identify cycles. - -Sources: - http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:detrended_price_osci - -Calculation: - Default Inputs: - length=1, centered=True - SMA = Simple Moving Average - drift = int(0.5 * length) + 1 - - DPO = close.shift(drift) - SMA(close, length) - if centered: - DPO = DPO.shift(-drift) - -Args: - close (pd.Series): Series of 'close's - length (int): It's period. Default: 1 - centered (bool): Shift the dpo back by int(0.5 * length) + 1. 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. -""" - - -increasing.__doc__ = \ -"""Increasing - -Returns True or False if the series is increasing over a periods. By default, -it returns True and False as 1 and 0 respectively with kwarg 'asint'. - -Sources: - -Calculation: - increasing = close.diff(length) > 0 - if asint: - increasing = increasing.astype(int) - -Args: - close (pd.Series): Series of 'close's - length (int): It's period. Default: 1 - asint (bool): Returns as binary. 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. -""" - - -qstick.__doc__ = \ -"""Q Stick - -The Q Stick indicator, developed by Tushar Chande, attempts to quantify and identify -trends in candlestick charts. - -Sources: - https://library.tradingtechnologies.com/trade/chrt-ti-qstick.html - -Calculation: - Default Inputs: - length=10 - xMA is one of: sma (default), dema, ema, hma, rma - qstick = xMA(close - open, length) - -Args: - open (pd.Series): Series of 'open's - close (pd.Series): Series of 'close's - length (int): It's period. Default: 1 - ma (str): The type of moving average to use. Default: None, which is 'sma' - 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. -""" - - -vortex.__doc__ = \ -"""Vortex - -Two oscillators that capture positive and negative trend movement. - -Sources: - https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:vortex_indicator - -Calculation: - Default Inputs: - length=14, drift=1 - TR = True Range - SMA = Simple Moving Average - tr = TR(high, low, close) - tr_sum = tr.rolling(length).sum() - - vmp = (high - low.shift(drift)).abs() - vmn = (low - high.shift(drift)).abs() - - VIP = vmp.rolling(length).sum() / tr_sum - VIM = vmn.rolling(length).sum() / tr_sum - -Args: - high (pd.Series): Series of 'high's - low (pd.Series): Series of 'low's - close (pd.Series): Series of 'close's - length (int): ROC 1 period. Default: 14 - drift (int): The difference 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.DataFrame: vip and vim columns -""" \ No newline at end of file diff --git a/pandas_ta/trend/__init__.py b/pandas_ta/trend/__init__.py new file mode 100644 index 0000000..7c68785 --- /dev/null +++ b/pandas_ta/trend/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/pandas_ta/trend/adx.py b/pandas_ta/trend/adx.py new file mode 100644 index 0000000..56e92a5 --- /dev/null +++ b/pandas_ta/trend/adx.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +from pandas import DataFrame +from ..overlap import rma +from ..volatility.atr import atr +from ..utils import get_drift, get_offset, verify_series, zero + +def adx(high, low, close, length=None, drift=None, offset=None, **kwargs): + """Indicator: ADX""" + # Validate Arguments + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + length = length if length and length > 0 else 14 + drift = get_drift(drift) + offset = get_offset(offset) + + # Calculate Result + _atr = atr(high=high, low=low, close=close, length=length) + + up = high - high.shift(drift) + dn = low.shift(drift) - low + + pos = ((up > dn) & (up > 0)) * up + neg = ((dn > up) & (dn > 0)) * dn + + pos = pos.apply(zero) + neg = neg.apply(zero) + + dmp = (100 / _atr) * rma(close=pos, length=length) + dmn = (100 / _atr) * rma(close=neg, length=length) + + dx = 100 * (dmp - dmn).abs() / (dmp + dmn) + adx = rma(close=dx, length=length) + + # Offset + if offset != 0: + dmp = dmp.shift(offset) + dmn = dmn.shift(offset) + adx = adx.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + adx.fillna(kwargs['fillna'], inplace=True) + dmp.fillna(kwargs['fillna'], inplace=True) + dmn.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + adx.fillna(method=kwargs['fill_method'], inplace=True) + dmp.fillna(method=kwargs['fill_method'], inplace=True) + dmn.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + adx.name = f"ADX_{length}" + dmp.name = f"DMP_{length}" + dmn.name = f"DMN_{length}" + + adx.category = dmp.category = dmn.category = 'trend' + + # Prepare DataFrame to return + data = {adx.name: adx, dmp.name: dmp, dmn.name: dmn} + adxdf = DataFrame(data) + adxdf.name = f"ADX_{length}" + adxdf.category = 'trend' + + return adxdf + + + +adx.__doc__ = \ +"""Average Directional Movement (ADX) + +Average Directional Movement is meant to quantify trend strength by measuring +the amount of movement in a single direction. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/average-directional-movement-adx/ + +Calculation: + DMI ADX TREND 2.0 by @TraderR0BERT, NETWORTHIE.COM + //Created by @TraderR0BERT, NETWORTHIE.COM, last updated 01/26/2016 + //DMI Indicator + //Resolution input option for higher/lower time frames + study(title="DMI ADX TREND 2.0", shorttitle="ADX TREND 2.0") + + adxlen = input(14, title="ADX Smoothing") + dilen = input(14, title="DI Length") + thold = input(20, title="Threshold") + + threshold = thold + + //Script for Indicator + dirmov(len) => + up = change(high) + down = -change(low) + truerange = rma(tr, len) + plus = fixnan(100 * rma(up > down and up > 0 ? up : 0, len) / truerange) + minus = fixnan(100 * rma(down > up and down > 0 ? down : 0, len) / truerange) + [plus, minus] + + adx(dilen, adxlen) => + [plus, minus] = dirmov(dilen) + sum = plus + minus + adx = 100 * rma(abs(plus - minus) / (sum == 0 ? 1 : sum), adxlen) + [adx, plus, minus] + + [sig, up, down] = adx(dilen, adxlen) + osob=input(40,title="Exhaustion Level for ADX, default = 40") + col = sig >= sig[1] ? green : sig <= sig[1] ? red : gray + + //Plot Definitions Current Timeframe + p1 = plot(sig, color=col, linewidth = 3, title="ADX") + p2 = plot(sig, color=col, style=circles, linewidth=3, title="ADX") + p3 = plot(up, color=blue, linewidth = 3, title="+DI") + p4 = plot(up, color=blue, style=circles, linewidth=3, title="+DI") + p5 = plot(down, color=fuchsia, linewidth = 3, title="-DI") + p6 = plot(down, color=fuchsia, style=circles, linewidth=3, title="-DI") + h1 = plot(threshold, color=black, linewidth =3, title="Threshold") + + trender = (sig >= up or sig >= down) ? 1 : 0 + bgcolor(trender>0?black:gray, transp=85) + + //Alert Function for ADX crossing Threshold + Up_Cross = crossover(up, threshold) + alertcondition(Up_Cross, title="DMI+ cross", message="DMI+ Crossing Threshold") + Down_Cross = crossover(down, threshold) + alertcondition(Down_Cross, title="DMI- cross", message="DMI- Crossing Threshold") + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + length (int): It's period. Default: 14 + drift (int): The difference 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.DataFrame: adx, dmp, dmn columns. +""" \ No newline at end of file diff --git a/pandas_ta/trend/amat.py b/pandas_ta/trend/amat.py new file mode 100644 index 0000000..99a3000 --- /dev/null +++ b/pandas_ta/trend/amat.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +from pandas import DataFrame +from .long_run import long_run +from ..overlap import ema, hma, linreg, rma, sma, wma +from .short_run import short_run +from ..utils import get_offset, verify_series + +def amat(close=None, fast=None, slow=None, mamode=None, lookback=None, offset=None, **kwargs): + """Indicator: Archer Moving Averages Trends (AMAT)""" + # Validate Arguments + close = verify_series(close) + fast = int(fast) if fast and fast > 0 else 8 + slow = int(slow) if slow and slow > 0 else 21 + lookback = int(lookback) if lookback and lookback > 0 else 2 + mamode = mamode.upper() if mamode else 'EMA' + offset = get_offset(offset) + + # Calculate Result + if mamode == 'EMA': + fast_ma = ema(close=close, length=fast, **kwargs) + slow_ma = ema(close=close, length=slow, **kwargs) + elif mamode == 'HMA': + fast_ma = hma(close=close, length=fast, **kwargs) + slow_ma = hma(close=close, length=slow, **kwargs) + elif mamode == 'LINREG': + fast_ma = linreg(close=close, length=fast, **kwargs) + slow_ma = linreg(close=close, length=slow, **kwargs) + elif mamode == 'RMA': + fast_ma = rma(close=close, length=fast, **kwargs) + slow_ma = rma(close=close, length=slow, **kwargs) + elif mamode == 'SMA': + fast_ma = sma(close=close, length=fast, **kwargs) + slow_ma = sma(close=close, length=slow, **kwargs) + elif mamode == 'WMA': + fast_ma = wma(close=close, length=fast, **kwargs) + slow_ma = wma(close=close, length=slow, **kwargs) + + mas_long = long_run(fast_ma, slow_ma, length=lookback) + mas_short = short_run(fast_ma, slow_ma, length=lookback) + + # Offset + if offset != 0: + mas_long = mas_long.shift(offset) + mas_short = mas_short.shift(offset) + + # # Handle fills + if 'fillna' in kwargs: + mas_long.fillna(kwargs['fillna'], inplace=True) + mas_short.fillna(kwargs['fillna'], inplace=True) + + if 'fill_method' in kwargs: + mas_long.fillna(method=kwargs['fill_method'], inplace=True) + mas_short.fillna(method=kwargs['fill_method'], inplace=True) + + # Prepare DataFrame to return + amatdf = DataFrame({ + f"AMAT_{mas_long.name}": mas_long, + f"AMAT_{mas_short.name}": mas_short + }) + + # Name and Categorize it + amatdf.name = f"AMAT_{mamode}_{fast}_{slow}_{lookback}" + amatdf.category = 'trend' + + return amatdf \ No newline at end of file diff --git a/pandas_ta/trend/aroon.py b/pandas_ta/trend/aroon.py new file mode 100644 index 0000000..5fed6cf --- /dev/null +++ b/pandas_ta/trend/aroon.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +from numpy import argmax as npargmax +from numpy import argmin as npargmin +from pandas import DataFrame +from ..utils import get_offset, verify_series + +def aroon(close, length=None, offset=None, **kwargs): + """Indicator: Aroon Oscillator""" + # Validate Arguments + close = verify_series(close) + length = length if length and length > 0 else 14 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + offset = get_offset(offset) + + # Calculate Result + def maxidx(x): + return 100 * (int(npargmax(x)) + 1) / length + + def minidx(x): + return 100 * (int(npargmin(x)) + 1) / length + + _close = close.rolling(length, min_periods=min_periods) + aroon_up = _close.apply(maxidx, raw=True) + aroon_down = _close.apply(minidx, raw=True) + + # Handle fills + if 'fillna' in kwargs: + aroon_up.fillna(kwargs['fillna'], inplace=True) + aroon_down.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + aroon_up.fillna(method=kwargs['fill_method'], inplace=True) + aroon_down.fillna(method=kwargs['fill_method'], inplace=True) + + # Offset + if offset != 0: + aroon_up = aroon_up.shift(offset) + aroon_down = aroon_down.shift(offset) + + # Name and Categorize it + aroon_up.name = f"AROONU_{length}" + aroon_down.name = f"AROOND_{length}" + + aroon_down.category = aroon_up.category = 'trend' + + # Prepare DataFrame to return + data = {aroon_down.name: aroon_down, aroon_up.name: aroon_up} + aroondf = DataFrame(data) + aroondf.name = f"AROON_{length}" + aroondf.category = 'trend' + + return aroondf + + + +aroon.__doc__ = \ +"""Aroon (AROON) + +Aroon attempts to identify if a security is trending and how strong. + +Sources: + https://www.tradingview.com/wiki/Aroon + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/aroon-ar/ + +Calculation: + Default Inputs: + length=1 + def maxidx(x): + return 100 * (int(np.argmax(x)) + 1) / length + + def minidx(x): + return 100 * (int(np.argmin(x)) + 1) / length + + _close = close.rolling(length, min_periods=min_periods) + aroon_up = _close.apply(maxidx, raw=True) + aroon_down = _close.apply(minidx, raw=True) + +Args: + close (pd.Series): Series of 'close'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.DataFrame: aroon_up, aroon_down columns. +""" diff --git a/pandas_ta/trend/decreasing.py b/pandas_ta/trend/decreasing.py new file mode 100644 index 0000000..1f31e0b --- /dev/null +++ b/pandas_ta/trend/decreasing.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from ..utils import get_offset, verify_series + +def decreasing(close, length=None, asint=True, offset=None, **kwargs): + """Indicator: Decreasing""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 1 + offset = get_offset(offset) + + # Calculate Result + decreasing = close.diff(length) < 0 + if asint: + decreasing = decreasing.astype(int) + + # Offset + if offset != 0: + decreasing = decreasing.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + decreasing.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + decreasing.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + decreasing.name = f"DEC_{length}" + decreasing.category = 'trend' + + return decreasing + + + +decreasing.__doc__ = \ +"""Decreasing + +Returns True or False if the series is decreasing over a periods. By default, +it returns True and False as 1 and 0 respectively with kwarg 'asint'. + +Sources: + +Calculation: + decreasing = close.diff(length) < 0 + if asint: + decreasing = decreasing.astype(int) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 1 + asint (bool): Returns as binary. 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. +""" \ No newline at end of file diff --git a/pandas_ta/trend/dpo.py b/pandas_ta/trend/dpo.py new file mode 100644 index 0000000..e5cb002 --- /dev/null +++ b/pandas_ta/trend/dpo.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +from ..utils import get_offset, verify_series + +def dpo(close, length=None, centered=True, offset=None, **kwargs): + """Indicator: Detrend Price Oscillator (DPO)""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 1 + 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 + drift = int(0.5 * length) + 1 # int((0.5 * length) + 1) + dpo = close.shift(drift) - close.rolling(length, min_periods=min_periods).mean() + if centered: + dpo = dpo.shift(-drift) + + # Offset + if offset != 0: + dpo = dpo.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + dpo.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + dpo.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + dpo.name = f"DPO_{length}" + dpo.category = 'trend' + + return dpo + + + +dpo.__doc__ = \ +"""Detrend Price Oscillator (DPO) + +Is an indicator designed to remove trend from price and make it easier to +identify cycles. + +Sources: + http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:detrended_price_osci + +Calculation: + Default Inputs: + length=1, centered=True + SMA = Simple Moving Average + drift = int(0.5 * length) + 1 + + DPO = close.shift(drift) - SMA(close, length) + if centered: + DPO = DPO.shift(-drift) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 1 + centered (bool): Shift the dpo back by int(0.5 * length) + 1. 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. +""" \ No newline at end of file diff --git a/pandas_ta/trend/increasing.py b/pandas_ta/trend/increasing.py new file mode 100644 index 0000000..568efbd --- /dev/null +++ b/pandas_ta/trend/increasing.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from ..utils import get_offset, verify_series + +def increasing(close, length=None, asint=True, offset=None, **kwargs): + """Indicator: Increasing""" + # Validate Arguments + close = verify_series(close) + length = int(length) if length and length > 0 else 1 + offset = get_offset(offset) + + # Calculate Result + increasing = close.diff(length) > 0 + if asint: + increasing = increasing.astype(int) + + # Offset + if offset != 0: + increasing = increasing.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + increasing.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + increasing.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + increasing.name = f"INC_{length}" + increasing.category = 'trend' + + return increasing + + + +increasing.__doc__ = \ +"""Increasing + +Returns True or False if the series is increasing over a periods. By default, +it returns True and False as 1 and 0 respectively with kwarg 'asint'. + +Sources: + +Calculation: + increasing = close.diff(length) > 0 + if asint: + increasing = increasing.astype(int) + +Args: + close (pd.Series): Series of 'close's + length (int): It's period. Default: 1 + asint (bool): Returns as binary. 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. +""" \ No newline at end of file diff --git a/pandas_ta/trend/long_run.py b/pandas_ta/trend/long_run.py new file mode 100644 index 0000000..4cc2e96 --- /dev/null +++ b/pandas_ta/trend/long_run.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from .decreasing import decreasing +from .increasing import increasing +from ..utils import get_offset, verify_series + +def long_run(fast, slow, length=None, offset=None, **kwargs): + """Indicator: Long Run""" + # Validate Arguments + fast = verify_series(fast) + slow = verify_series(slow) + length = int(length) if length and length > 0 else 2 + offset = get_offset(offset) + + # Calculate Result + pb = increasing(fast, length) & decreasing(slow, length) # potential bottom or bottom + bi = increasing(fast, length) & increasing(slow, length) # fast and slow are increasing + long_run = pb | bi + + # Offset + if offset != 0: + long_run = long_run.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + long_run.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + long_run.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + long_run.name = f"LR_{length}" + long_run.category = 'trend' + + return long_run \ No newline at end of file diff --git a/pandas_ta/trend/qstick.py b/pandas_ta/trend/qstick.py new file mode 100644 index 0000000..d5ceeb0 --- /dev/null +++ b/pandas_ta/trend/qstick.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +from ..overlap import dema, ema, hma, rma, sma +from ..utils import get_offset, verify_series + +def qstick(open_, close, length=None, offset=None, **kwargs): + """Indicator: Q Stick""" + # Validate Arguments + open_ = verify_series(open_) + close = verify_series(close) + length = int(length) if length and length > 0 else 10 + offset = get_offset(offset) + ma = kwargs.pop('ma', 'sma') if 'ma' in kwargs else 'sma' + + # Calculate Result + diff = close - open_ + + if ma in [None, 'sma']: qstick = sma(diff, length=length) + if ma == 'dema': qstick = dema(diff, length=length, **kwargs) + if ma == 'ema': qstick = ema(diff, length=length, **kwargs) + if ma == 'hma': qstick = hma(diff, length=length) + if ma == 'rma': qstick = rma(diff, length=length) + + # Offset + if offset != 0: + qstick = qstick.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + qstick.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + qstick.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + qstick.name = f"QS_{length}" + qstick.category = 'trend' + + return qstick + + + +qstick.__doc__ = \ +"""Q Stick + +The Q Stick indicator, developed by Tushar Chande, attempts to quantify and identify +trends in candlestick charts. + +Sources: + https://library.tradingtechnologies.com/trade/chrt-ti-qstick.html + +Calculation: + Default Inputs: + length=10 + xMA is one of: sma (default), dema, ema, hma, rma + qstick = xMA(close - open, length) + +Args: + open (pd.Series): Series of 'open's + close (pd.Series): Series of 'close's + length (int): It's period. Default: 1 + ma (str): The type of moving average to use. Default: None, which is 'sma' + 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. +""" \ No newline at end of file diff --git a/pandas_ta/trend/short_run.py b/pandas_ta/trend/short_run.py new file mode 100644 index 0000000..966c98d --- /dev/null +++ b/pandas_ta/trend/short_run.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from .decreasing import decreasing +from .increasing import increasing +from ..utils import get_offset, verify_series + +def short_run(fast, slow, length=None, offset=None, **kwargs): + """Indicator: Short Run""" + # Validate Arguments + fast = verify_series(fast) + slow = verify_series(slow) + length = int(length) if length and length > 0 else 2 + offset = get_offset(offset) + + # Calculate Result + pt = decreasing(fast, length) & increasing(slow, length) # potential top or top + bd = decreasing(fast, length) & decreasing(slow, length) # fast and slow are decreasing + short_run = pt | bd + + # Offset + if offset != 0: + short_run = short_run.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + short_run.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + short_run.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + short_run.name = f"SR_{length}" + short_run.category = 'trend' + + return short_run \ No newline at end of file diff --git a/pandas_ta/trend/vortex.py b/pandas_ta/trend/vortex.py new file mode 100644 index 0000000..695fbe1 --- /dev/null +++ b/pandas_ta/trend/vortex.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +from pandas import DataFrame +from ..volatility.true_range import true_range +from ..utils import get_drift, get_offset, verify_series, zero + +def vortex(high, low, close, length=None, drift=None, offset=None, **kwargs): + """Indicator: Vortex""" + # Validate arguments + high = verify_series(high) + low = verify_series(low) + close = verify_series(close) + length = length if length and length > 0 else 14 + min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length + drift = get_drift(drift) + offset = get_offset(offset) + + # Calculate Result + tr = true_range(high=high, low=low, close=close) + tr_sum = tr.rolling(length, min_periods=min_periods).sum() + + vmp = (high - low.shift(drift)).abs() + vmm = (low - high.shift(drift)).abs() + + vip = vmp.rolling(length, min_periods=min_periods).sum() / tr_sum + vim = vmm.rolling(length, min_periods=min_periods).sum() / tr_sum + + # Offset + if offset != 0: + vip = vip.shift(offset) + vim = vim.shift(offset) + + # Handle fills + if 'fillna' in kwargs: + vip.fillna(kwargs['fillna'], inplace=True) + vim.fillna(kwargs['fillna'], inplace=True) + if 'fill_method' in kwargs: + vip.fillna(method=kwargs['fill_method'], inplace=True) + vim.fillna(method=kwargs['fill_method'], inplace=True) + + # Name and Categorize it + vip.name = f"VTXP_{length}" + vim.name = f"VTXM_{length}" + vip.category = vim.category = 'trend' + + # Prepare DataFrame to return + data = {vip.name: vip, vim.name: vim} + vtxdf = DataFrame(data) + vtxdf.name = f"VTX_{length}" + vtxdf.category = 'trend' + + return vtxdf + + + +vortex.__doc__ = \ +"""Vortex + +Two oscillators that capture positive and negative trend movement. + +Sources: + https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:vortex_indicator + +Calculation: + Default Inputs: + length=14, drift=1 + TR = True Range + SMA = Simple Moving Average + tr = TR(high, low, close) + tr_sum = tr.rolling(length).sum() + + vmp = (high - low.shift(drift)).abs() + vmn = (low - high.shift(drift)).abs() + + VIP = vmp.rolling(length).sum() / tr_sum + VIM = vmn.rolling(length).sum() / tr_sum + +Args: + high (pd.Series): Series of 'high's + low (pd.Series): Series of 'low's + close (pd.Series): Series of 'close's + length (int): ROC 1 period. Default: 14 + drift (int): The difference 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.DataFrame: vip and vim columns +""" \ No newline at end of file diff --git a/pandas_ta/volume/aobv.py b/pandas_ta/volume/aobv.py index c1f67da..a6f33ce 100644 --- a/pandas_ta/volume/aobv.py +++ b/pandas_ta/volume/aobv.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- from .obv import obv from ..overlap import * -from ..trend import long_run, short_run +from ..trend.long_run import long_run +from ..trend.short_run import short_run from ..utils import get_offset, verify_series def aobv(close, volume, fast=None, slow=None, mamode=None, max_lookback=None, min_lookback=None, offset=None, **kwargs): diff --git a/setup.py b/setup.py index 6910c30..c32379d 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ long_description = "An easy to use Python 3 Pandas Extension of Technical Analys setup( name = "pandas_ta", packages = ["pandas_ta"], - version = "0.1.23b", + version = "0.1.24b", description=long_description, long_description=long_description, author = "Kevin Johnson", diff --git a/tests/test_indicator_trend.py b/tests/test_indicator_trend.py index 70afd12..eae31eb 100644 --- a/tests/test_indicator_trend.py +++ b/tests/test_indicator_trend.py @@ -30,15 +30,12 @@ class TestTrend(TestCase): del cls.data - def setUp(self): - self.trend = pandas_ta.trend - - def tearDown(self): - del self.trend + def setUp(self): pass + def tearDown(self): pass def test_adx(self): - result = self.trend.adx(self.high, self.low, self.close) + result = pandas_ta.adx(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, 'ADX_14') @@ -53,12 +50,12 @@ class TestTrend(TestCase): error_analysis(result, CORRELATION, ex) def test_amat(self): - result = self.trend.amat(self.close) + result = pandas_ta.amat(self.close) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, 'AMAT_EMA_8_21_2') def test_aroon(self): - result = self.trend.aroon(self.close) + result = pandas_ta.aroon(self.close) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, 'AROON_14') @@ -80,36 +77,36 @@ class TestTrend(TestCase): error_analysis(result.iloc[:,1], CORRELATION, ex, newline=False) def test_decreasing(self): - result = self.trend.decreasing(self.close) + result = pandas_ta.decreasing(self.close) self.assertIsInstance(result, Series) self.assertEqual(result.name, 'DEC_1') def test_dpo(self): - result = self.trend.dpo(self.close) + result = pandas_ta.dpo(self.close) self.assertIsInstance(result, Series) self.assertEqual(result.name, 'DPO_1') def test_increasing(self): - result = self.trend.increasing(self.close) + result = pandas_ta.increasing(self.close) self.assertIsInstance(result, Series) self.assertEqual(result.name, 'INC_1') def test_long_run(self): - result = self.trend.long_run(self.close, self.open) + result = pandas_ta.long_run(self.close, self.open) self.assertIsInstance(result, Series) self.assertEqual(result.name, 'LR_2') def test_qstick(self): - result = self.trend.qstick(self.open, self.close) + result = pandas_ta.qstick(self.open, self.close) self.assertIsInstance(result, Series) self.assertEqual(result.name, 'QS_10') def test_short_run(self): - result = self.trend.short_run(self.close, self.open) + result = pandas_ta.short_run(self.close, self.open) self.assertIsInstance(result, Series) self.assertEqual(result.name, 'SR_2') def test_vortex(self): - result = self.trend.vortex(self.high, self.low, self.close) + result = pandas_ta.vortex(self.high, self.low, self.close) self.assertIsInstance(result, DataFrame) self.assertEqual(result.name, 'VTX_14') \ No newline at end of file