mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-08-19 12:30:41 +08:00
ENH + MAINT added Heikin-Ashi and Supertrend
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|:--------:|
|
||||
|  |
|
||||
|
||||
## _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.
|
||||
Please leave any comments, feedback, suggestions, or indicator requests.
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .ha import ha
|
||||
@@ -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.
|
||||
"""
|
||||
+29
-21
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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'])
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user