BUG ENH DEV PERF TST STY overhaul

This commit is contained in:
Kevin Johnson
2022-01-24 09:33:33 -08:00
parent 0dac65775c
commit 8acf289f2b
185 changed files with 4041 additions and 3141 deletions
+357 -311
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+87 -87
View File
File diff suppressed because one or more lines are too long
+22 -113
View File
@@ -2,121 +2,30 @@ name = "pandas_ta"
"""
.. moduleauthor:: Kevin Johnson
"""
from importlib.util import find_spec
from pathlib import Path
from pkg_resources import get_distribution, DistributionNotFound
# Dictionaries and version
from pandas_ta.maps import EXCHANGE_TZ, RATE, Category, Imports, version
from pandas_ta.utils import *
# Flat Structure. Supports ta.ema() or ta.overlap.ema() calls.
from pandas_ta.candles import *
from pandas_ta.cycles import *
from pandas_ta.momentum import *
from pandas_ta.overlap import *
from pandas_ta.performance import *
from pandas_ta.statistics import *
from pandas_ta.trend import *
from pandas_ta.volatility import *
from pandas_ta.volume import *
_dist = get_distribution("pandas_ta")
try:
# Normalize case for Windows systems
here = Path(_dist.location) / __file__
if not here.exists():
# not installed, but there is another version that *is*
raise DistributionNotFound
except DistributionNotFound:
__version__ = "Please install this project with setup.py"
# Common Averages useful for Indicators with a mamode argument, like ta.adx()
from pandas_ta.ma import ma
version = __version__ = _dist.version
# Enable "ta" DataFrame Extension
from pandas_ta.core import AnalysisIndicators
Imports = {
"alphaVantage-api": find_spec("alphaVantageAPI") is not None,
"matplotlib": find_spec("matplotlib") is not None,
"mplfinance": find_spec("mplfinance") is not None,
"numba": find_spec("numba") is not None,
"yaml": find_spec("yaml") is not None,
"scipy": find_spec("scipy") is not None,
"sklearn": find_spec("sklearn") is not None,
"statsmodels": find_spec("statsmodels") is not None,
"stochastic": find_spec("stochastic") is not None,
"talib": find_spec("talib") is not None,
"tqdm": find_spec("tqdm") is not None,
"vectorbt": find_spec("vectorbt") is not None,
"yfinance": find_spec("yfinance") is not None,
"polygon": find_spec("polygon") is not None,
}
# Custom External Directory Commands. See help(import_dir)
from pandas_ta.custom import create_dir, import_dir
# Not ideal and not dynamic but it works.
# Will find a dynamic solution later.
Category = {
# Candles
"candles": [
"cdl_pattern", "cdl_z", "ha"
],
# Cycles
"cycles": ["ebsw", "reflex"],
# Momentum
"momentum": [
"ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo",
"coppock", "cti", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd",
"mom", "pgo", "ppo", "psl", "pvo", "qqe", "roc", "rsi", "rsx", "rvgi",
"slope", "smi", "squeeze", "squeeze_pro", "stc", "stoch", "stochf",
"stochrsi", "td_seq", "trix", "tsi", "uo", "willr"
],
# Overlap
"overlap": [
"alligator", "alma", "dema", "ema", "fwma", "hilo", "hl2", "hlc3",
"hma", "hwma", "ichimoku", "jma", "kama", "linreg", "mcgd", "midpoint",
"midprice", "ohlc4", "pwma", "rma", "sinwma", "sma", "smma", "ssf",
"ssf3", "supertrend", "swma", "t3", "tema", "trima", "vidya", "vwap",
"vwma", "wcp", "wma", "zlma"
],
# Performance
"performance": ["log_return", "percent_return"],
# Statistics
"statistics": [
"entropy", "kurtosis", "mad", "median", "quantile", "skew", "stdev",
"tos_stdevall", "variance", "zscore"
],
# Trend
"trend": [
"adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", "dpo",
"increasing", "long_run", "psar", "qstick", "short_run", "trendflex", "tsignals",
"ttm_trend", "vhf", "vortex", "xsignals"
],
# Volatility
"volatility": [
"aberration", "accbands", "atr", "bbands", "donchian", "hwc", "kc", "massi",
"natr", "pdist", "rvi", "thermo", "true_range", "ui"
],
# Volume.
# Note: "vp" or "Volume Profile" is excluded since it does not return a Time Series
"volume": [
"ad", "adosc", "aobv", "cmf", "efi", "eom", "kvo", "mfi", "nvi", "obv",
"pvi", "pvol", "pvr", "pvt", "wb_tsv"
],
}
CANGLE_AGG = {
"open": "first",
"high": "max",
"low": "min",
"close": "last",
"volume": "sum"
}
# https://www.worldtimezone.com/markets24.php
EXCHANGE_TZ = {
"NZSX": 12, "ASX": 11,
"TSE": 9, "HKE": 8, "SSE": 8, "SGX": 8,
"NSE": 5.5, "DIFX": 4, "RTS": 3,
"JSE": 2, "FWB": 1, "LSE": 1,
"BMF": -2, "NYSE": -4, "TSX": -4,
"GENR": 0 # Generated Data
}
RATE = {
"DAYS_PER_MONTH": 21,
"MINUTES_PER_HOUR": 60,
"MONTHS_PER_YEAR": 12,
"QUARTERS_PER_YEAR": 4,
"TRADING_DAYS_PER_YEAR": 252, # Keep even
"TRADING_HOURS_PER_DAY": 6.5,
"WEEKS_PER_YEAR": 52,
"YEARLY": 1,
}
import numpy as np
import pandas as pd
from pandas_ta.core import *
# Empty DataFrame Alias. Example:
# >> ta.df.ta.ticker("spy")
df = DataFrame()
+11 -7
View File
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
from pandas import Series
from pandas_ta.overlap import sma
from pandas_ta.utils import get_offset, high_low_range, is_percent
from pandas_ta.utils import real_body, verify_series
from pandas import Series
def cdl_doji(open_: Series, high: Series, low: Series, close: Series, length: int = None, factor: float = None,
scalar: float = None, asint: bool = True, offset: int = None, **kwargs) -> Series:
def cdl_doji(
open_: Series, high: Series, low: Series, close: Series,
length: int = None, factor: float = None, scalar: float = None,
asint: bool = True,
offset: int = None, **kwargs
) -> Series:
"""Candle Type: Doji
A candle body is Doji, when it's shorter than 10% of the
@@ -35,7 +39,7 @@ def cdl_doji(open_: Series, high: Series, low: Series, close: Series, length: in
Returns:
pd.Series: CDL_DOJI column.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
factor = float(factor) if is_percent(factor) else 10
scalar = float(scalar) if scalar else 100
@@ -48,7 +52,7 @@ def cdl_doji(open_: Series, high: Series, low: Series, close: Series, length: in
if open_ is None or high is None or low is None or close is None: return
# Calculate Result
# Calculate
body = real_body(open_, close).abs()
hl_range = high_low_range(high, low).abs()
hl_range_avg = sma(hl_range, length)
@@ -63,13 +67,13 @@ def cdl_doji(open_: Series, high: Series, low: Series, close: Series, length: in
if offset != 0:
doji = doji.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
doji.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
doji.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
doji.name = f"CDL_DOJI_{length}_{0.01 * factor}"
doji.category = "candles"
+13 -9
View File
@@ -1,15 +1,19 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import candle_color, get_offset
from pandas_ta.utils import verify_series
from pandas import Series
from pandas_ta.utils import candle_color, get_offset, verify_series
def cdl_inside(open_: Series, high: Series, low: Series, close: Series, asbool: bool = False,
offset: int = None, **kwargs) -> Series:
def cdl_inside(
open_: Series, high: Series, low: Series, close: Series,
asbool: bool = False,
offset: int = None, **kwargs
) -> Series:
"""Candle Type: Inside Bar
An Inside Bar is a bar that is engulfed by the prior highs and lows of it's
previous bar. In other words, the current bar is smaller than it's previous bar.
previous bar. In other words, the current bar is smaller than it's previous
bar.
Set asbool=True if you want to know if it is an Inside Bar. Note by default
asbool=False so this returns a 0 if it is not an Inside Bar, 1 if it is an
Inside Bar and close > open, and -1 if it is an Inside Bar but close < open.
@@ -32,14 +36,14 @@ def cdl_inside(open_: Series, high: Series, low: Series, close: Series, asbool:
Returns:
pd.Series: New feature
"""
# Validate arguments
# Validate
open_ = verify_series(open_)
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
offset = get_offset(offset)
# Calculate Result
# Calculate
inside = (high.diff() < 0) & (low.diff() > 0)
if not asbool:
@@ -49,13 +53,13 @@ def cdl_inside(open_: Series, high: Series, low: Series, close: Series, asbool:
if offset != 0:
inside = inside.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
inside.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
inside.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
inside.name = f"CDL_INSIDE"
inside.category = "candles"
+7 -13
View File
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
from typing import Sequence, Union
from pandas import Series, DataFrame
from . import cdl_doji, cdl_inside
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas_ta import Imports
from pandas_ta.candles import cdl_doji, cdl_inside
ALL_PATTERNS = [
@@ -24,14 +23,9 @@ ALL_PATTERNS = [
def cdl_pattern(
open_: Series,
high: Series,
low: Series,
close: Series,
name: Union[str, Sequence[str]] = "all",
scalar: float = None,
offset: int = None,
**kwargs
open_: Series, high: Series, low: Series, close: Series,
name: Union[str, Sequence[str]] = "all", scalar: float = None,
offset: int = None, **kwargs
) -> DataFrame:
"""TA Lib Candle Patterns
@@ -105,7 +99,7 @@ def cdl_pattern(
if offset != 0:
pattern_result = pattern_result.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
pattern_result.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
@@ -115,7 +109,7 @@ def cdl_pattern(
if len(result) == 0: return
# Prepare DataFrame to return
# Name and Category
df = DataFrame(result)
df.name = "CDL_PATTERN"
df.category = "candles"
+9 -6
View File
@@ -4,8 +4,11 @@ from pandas_ta.statistics import zscore
from pandas_ta.utils import get_offset, verify_series
def cdl_z(open_: Series, high: Series, low: Series, close: Series, length: int = None, full: bool = None,
ddof=None, offset: int = None, **kwargs) -> DataFrame:
def cdl_z(
open_: Series, high: Series, low: Series, close: Series,
length: int = None, full: bool = None, ddof=None,
offset: int = None, **kwargs
) -> DataFrame:
"""Candle Type: Z
Normalizes OHLC Candles with a rolling Z Score.
@@ -29,7 +32,7 @@ def cdl_z(open_: Series, high: Series, low: Series, close: Series, length: int =
Returns:
pd.Series: CDL_DOJI column.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 30
ddof = int(ddof) if ddof and ddof >= 0 and ddof < length else 1
open_ = verify_series(open_, length)
@@ -41,7 +44,7 @@ def cdl_z(open_: Series, high: Series, low: Series, close: Series, length: int =
if open_ is None or high is None or low is None or close is None: return
# Calculate Result
# Calculate
if full:
length = close.size
@@ -66,13 +69,13 @@ def cdl_z(open_: Series, high: Series, low: Series, close: Series, length: int =
if offset != 0:
df = df.shift(offset)
# Handle fills
# Fill
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
# Name and Category
df.name = f"CDL_Z{_props}"
df.category = "candles"
+8 -5
View File
@@ -3,7 +3,10 @@ from pandas import DataFrame, Series
from pandas_ta.utils import get_offset, verify_series
def ha(open_: Series, high: Series, low: Series, close: Series, offset: int = None, **kwargs) -> DataFrame:
def ha(
open_: Series, high: Series, low: Series, close: Series,
offset: int = None, **kwargs
) -> DataFrame:
"""Heikin Ashi Candles (HA)
The Heikin-Ashi technique averages price data to create a Japanese
@@ -32,14 +35,14 @@ def ha(open_: Series, high: Series, low: Series, close: Series, offset: int = No
Returns:
pd.DataFrame: ha_open, ha_high,ha_low, ha_close columns.
"""
# Validate Arguments
# Validate
open_ = verify_series(open_)
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
offset = get_offset(offset)
# Calculate Result
# Calculate
m = close.size
df = DataFrame({
"HA_open": 0.5 * (open_.iloc[0] + close.iloc[0]),
@@ -58,13 +61,13 @@ def ha(open_: Series, high: Series, low: Series, close: Series, offset: int = No
if offset != 0:
df = df.shift(offset)
# Handle fills
# Fill
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
# Name and Category
df.name = "Heikin-Ashi"
df.category = "candles"
-1
View File
@@ -595,7 +595,6 @@ class AnalysisIndicators(BasePandasObject):
# "data", # reserved
"long_run",
"short_run",
"td_seq", # Performance exclusion
"tsignals",
"xsignals",
]
+76 -81
View File
@@ -8,10 +8,9 @@ from os.path import abspath, join, exists, basename, splitext
from glob import glob
import pandas_ta
from pandas_ta import AnalysisIndicators
def bind(function_name: str, function: types.FunctionType, method: types.MethodType):
def bind(name: str, f: types.FunctionType):#, method: types.MethodType = None):
"""
Helper function to bind the function and class method defined in a custom
indicator module to the active pandas_ta instance.
@@ -21,8 +20,8 @@ def bind(function_name: str, function: types.FunctionType, method: types.MethodT
function (fcn): The indicator function
method (fcn): The class method corresponding to the passed function
"""
setattr(pandas_ta, function_name, function)
setattr(AnalysisIndicators, function_name, method)
setattr(pandas_ta, name, f)
setattr(pandas_ta.AnalysisIndicators, name, f)
def create_dir(path: str, create_categories: bool = True, verbose: bool = True):
@@ -81,6 +80,74 @@ def get_module_functions(module: types.ModuleType) -> dict:
def import_dir(path: str, verbose: bool = True):
"""
Import a directory of custom indicators into pandas_ta
Args:
path (str): Full path to your indicator tree
verbose (bool): If True verbose output of results
This method allows you to experiment and develop your own technical analysis
indicators in a separate local directory of your choice but use them seamlessly
together with the existing pandas_ta functions just like if they were part of
pandas_ta.
If you at some late point would like to push them into the pandas_ta library
you can do so very easily by following the step by step instruction here
https://github.com/twopirllc/pandas-ta/issues/355.
A brief example of usage:
1. Loading the 'ta' module:
>>> import pandas as pd
>>> import pandas_ta as ta
2. Create an empty directory on your machine where you want to work with your
indicators. Invoke pandas_ta.custom.import_dir once to pre-populate it with
sub-folders for all available indicator categories, e.g.:
>>> import os
>>> from os.path import abspath, join, expanduser
>>> from pandas_ta.custom import create_dir, import_dir
>>> ta_dir = abspath(join(expanduser("~"), "my_indicators"))
>>> create_dir(ta_dir)
3. You can now create your own custom indicator e.g. by copying existing
ones from pandas_ta core module and modifying them.
IMPORTANT: Each custom indicator should have a unique name and have both
a) a function named exactly as the module, e.g. 'ni' if the module is ni.py
b) a matching method used by AnalysisIndicators named as the module but
ending with '_method'. E.g. 'ni_method'
In essence these modules should look exactly like the standard indicators
available in categories under the pandas_ta-folder. The only difference will
be an addition of a matching class method.
For an example of the correct structure, look at the example ni.py in the
examples folder.
The ni.py indicator is a trend indicator so therefore we drop it into the
sub-folder named trend. Thus we have a folder structure like this:
~/my_indicators/
├── candles/
.
.
└── trend/
. └── ni.py
.
└── volume/
4. We can now dynamically load all our custom indicators located in our
designated indicators directory like this:
>>> import_dir(ta_dir)
If your custom indicator(s) loaded succesfully then it should behave exactly
like all other native indicators in pandas_ta, including help functions.
"""
# ensure that the passed directory exists / is readable
if not exists(path):
print(f"[X] Unable to read the directory '{path}'.")
@@ -111,13 +178,13 @@ def import_dir(path: str, verbose: bool = True):
module_functions = load_indicator_module(module_name)
# figure out which of the modules functions to bind to pandas_ta
fcn_callable = module_functions.get(module_name, None)
fcn_method_callable = module_functions.get(f"{module_name}_method", None)
_callable = module_functions.get(module_name, None)
_method_callable = module_functions.get(f"{module_name}_method", None)
if fcn_callable == None:
if _callable == None:
print(f"[X] Unable to find a function named '{module_name}' in the module '{module_name}.py'.")
continue
if fcn_method_callable == None:
if _method_callable == None:
missing_method = f"{module_name}_method"
print(f"[X] Unable to find a method function named '{missing_method}' in the module '{module_name}.py'.")
continue
@@ -126,82 +193,11 @@ def import_dir(path: str, verbose: bool = True):
if module_name not in pandas_ta.Category[dirname]:
pandas_ta.Category[dirname].append(module_name)
bind(module_name, fcn_callable, fcn_method_callable)
bind(module_name, _callable, _method_callable)
if verbose:
print(f"[i] Successfully imported the custom indicator '{module}' into category '{dirname}'.")
import_dir.__doc__ = \
"""
Import a directory of custom indicators into pandas_ta
Args:
path (str): Full path to your indicator tree
verbose (bool): If True verbose output of results
This method allows you to experiment and develop your own technical analysis
indicators in a separate local directory of your choice but use them seamlessly
together with the existing pandas_ta functions just like if they were part of
pandas_ta.
If you at some late point would like to push them into the pandas_ta library
you can do so very easily by following the step by step instruction here
https://github.com/twopirllc/pandas-ta/issues/355.
A brief example of usage:
1. Loading the 'ta' module:
>>> import pandas as pd
>>> import pandas_ta as ta
2. Create an empty directory on your machine where you want to work with your
indicators. Invoke pandas_ta.custom.import_dir once to pre-populate it with
sub-folders for all available indicator categories, e.g.:
>>> import os
>>> from os.path import abspath, join, expanduser
>>> from pandas_ta.custom import create_dir, import_dir
>>> ta_dir = abspath(join(expanduser("~"), "my_indicators"))
>>> create_dir(ta_dir)
3. You can now create your own custom indicator e.g. by copying existing
ones from pandas_ta core module and modifying them.
IMPORTANT: Each custom indicator should have a unique name and have both
a) a function named exactly as the module, e.g. 'ni' if the module is ni.py
b) a matching method used by AnalysisIndicators named as the module but
ending with '_method'. E.g. 'ni_method'
In essence these modules should look exactly like the standard indicators
available in categories under the pandas_ta-folder. The only difference will
be an addition of a matching class method.
For an example of the correct structure, look at the example ni.py in the
examples folder.
The ni.py indicator is a trend indicator so therefore we drop it into the
sub-folder named trend. Thus we have a folder structure like this:
~/my_indicators/
├── candles/
.
.
└── trend/
. └── ni.py
.
└── volume/
4. We can now dynamically load all our custom indicators located in our
designated indicators directory like this:
>>> import_dir(ta_dir)
If your custom indicator(s) loaded succesfully then it should behave exactly
like all other native indicators in pandas_ta, including help functions.
"""
def load_indicator_module(name: str) -> dict:
"""
Helper function to (re)load an indicator module.
@@ -214,7 +210,6 @@ def load_indicator_module(name: str) -> dict:
}
"""
# load module
try:
module = importlib.import_module(name)
except Exception as ex:
+33 -30
View File
@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
from pandas_ta import np, pd
from numpy import cos, exp, mean, nan, pi, roll, sin, sqrt, zeros
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def ebsw(close: Series, length: int = None, bars: int = None, offset: int = None, initial_version: bool = False,
**kwargs) -> Series:
def ebsw(
close: Series, length: int = None, bars: int = None,
initial_version: bool = False,
offset: int = None, **kwargs
) -> Series:
"""Even Better SineWave (EBSW)
This indicator measures market cycles and uses a low pass filter to remove noise.
@@ -23,7 +27,6 @@ def ebsw(close: Series, length: int = None, bars: int = None, offset: int = None
faster, than the corresponding reference value. This might be pre-roll related and was not further investigated.
* https://github.com/twopirllc/pandas-ta/issues/350
Sources:
- https://www.prorealcode.com/prorealtime-indicators/even-better-sinewave/
- J.F.Ehlers 'Cycle Analytics for Traders', 2014
@@ -43,7 +46,7 @@ def ebsw(close: Series, length: int = None, bars: int = None, offset: int = None
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if isinstance(length, int) and length > 10 else 40
bars = int(bars) if isinstance(bars, int) and bars > 0 else 10
close = verify_series(close, length)
@@ -51,9 +54,12 @@ def ebsw(close: Series, length: int = None, bars: int = None, offset: int = None
if close is None: return
# allow initial version to be used (more responsive/caution!)
initial_version = bool(initial_version) if isinstance(initial_version, bool) else False
# Calculate
# allow initial version to be used (more responsive/caution!)
m = close.size
initial_version = bool(initial_version) if isinstance(initial_version, bool) else False
if initial_version:
# not the default version that is active
alpha1 = hp = 0 # alpha and HighPass
@@ -62,17 +68,15 @@ def ebsw(close: Series, length: int = None, bars: int = None, offset: int = None
lastClose = lastHP = 0
filtHist = [0, 0] # Filter history
# Calculate Result
m = close.size
result = [np.nan for _ in range(0, length - 1)] + [0]
result = [nan for _ in range(0, length - 1)] + [0]
for i in range(length, m):
# HighPass filter cyclic components whose periods are shorter than Duration input
alpha1 = (1 - np.sin(360 / length)) / np.cos(360 / length)
alpha1 = (1 - sin(360 / length)) / cos(360 / length)
hp = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP
# Smooth with a Super Smoother Filter from equation 3-3
a1 = np.exp(-np.sqrt(2) * np.pi / bars)
b1 = 2 * a1 * np.cos(np.sqrt(2) * 180 / bars)
a1 = exp(-sqrt(2) * pi / bars)
b1 = 2 * a1 * cos(sqrt(2) * 180 / bars)
c2 = b1
c3 = -1 * a1 * a1
c1 = 1 - c2 - c3
@@ -93,31 +97,30 @@ def ebsw(close: Series, length: int = None, bars: int = None, offset: int = None
lastClose = close[i]
result.append(wave)
else: # this version is the default version
# Instance Variables
else: # this version is the default version
# Calculate
lastHP = lastClose = 0
filtHist = np.zeros(3)
result = [np.nan] * (length - 1) + [0]
filtHist = zeros(3)
result = [nan] * (length - 1) + [0]
# Calculate constants
angle = 2 * np.pi / length
alpha1 = (1 - np.sin(angle)) / np.cos(angle)
ang = 2 ** .5 * np.pi / bars
a1 = np.exp(-ang)
c2 = 2 * a1 * np.cos(ang)
angle = 2 * pi / length
alpha1 = (1 - sin(angle)) / cos(angle)
ang = 2 ** .5 * pi / bars
a1 = exp(-ang)
c2 = 2 * a1 * cos(ang)
c3 = -a1 ** 2
c1 = 1 - c2 - c3
for i in range(length, close.size):
for i in range(length, m):
hp = 0.5 * (1 + alpha1) * (close[i] - lastClose) + alpha1 * lastHP
# Rotate filters to overwrite oldest value
filtHist = np.roll(filtHist, -1)
filtHist = roll(filtHist, -1)
filtHist[-1] = 0.5 * c1 * (hp + lastHP) + c2 * filtHist[1] + c3 * filtHist[0]
# Wave calculation
wave = np.mean(filtHist)
rms = np.sqrt(np.mean(filtHist ** 2))
wave = mean(filtHist)
rms = sqrt(mean(filtHist ** 2))
wave = wave / rms
# Update past values
@@ -125,19 +128,19 @@ def ebsw(close: Series, length: int = None, bars: int = None, offset: int = None
lastClose = close[i]
result.append(wave)
ebsw = pd.Series(result, index=close.index)
ebsw = Series(result, index=close.index)
# Offset
if offset != 0:
ebsw = ebsw.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
ebsw.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
ebsw.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
ebsw.name = f"EBSW_{length}_{bars}"
ebsw.category = "cycles"
+21 -20
View File
@@ -1,30 +1,30 @@
# -*- coding: utf-8 -*-
from pandas import Series
from .dema import dema
from .ema import ema
from .fwma import fwma
from .hma import hma
from .linreg import linreg
from .midpoint import midpoint
from .pwma import pwma
from .rma import rma
from .sinwma import sinwma
from .sma import sma
from .swma import swma
from .t3 import t3
from .tema import tema
from .trima import trima
from .vidya import vidya
from .wma import wma
from pandas_ta.overlap.dema import dema
from pandas_ta.overlap.ema import ema
from pandas_ta.overlap.fwma import fwma
from pandas_ta.overlap.hma import hma
from pandas_ta.overlap.linreg import linreg
from pandas_ta.overlap.midpoint import midpoint
from pandas_ta.overlap.pwma import pwma
from pandas_ta.overlap.rma import rma
from pandas_ta.overlap.sinwma import sinwma
from pandas_ta.overlap.sma import sma
from pandas_ta.overlap.ssf import ssf
from pandas_ta.overlap.swma import swma
from pandas_ta.overlap.t3 import t3
from pandas_ta.overlap.tema import tema
from pandas_ta.overlap.trima import trima
from pandas_ta.overlap.vidya import vidya
from pandas_ta.overlap.wma import wma
def ma(name: str = None, source: Series = None, **kwargs) -> Series:
"""Simple MA Utility for easier MA selection
Available MAs:
dema, ema, fwma, hma, linreg, midpoint, pwma, rma,
sinwma, sma, swma, t3, tema, trima, vidya, wma
dema, ema, fwma, hma, linreg, midpoint, pwma, rma, sinwma, sma, ssf,
swma, t3, tema, trima, vidya, wma
Examples:
ema8 = ta.ma("ema", df.close, length=8)
@@ -44,7 +44,7 @@ def ma(name: str = None, source: Series = None, **kwargs) -> Series:
_mas = [
"dema", "ema", "fwma", "hma", "linreg", "midpoint", "pwma", "rma",
"sinwma", "sma", "swma", "t3", "tema", "trima", "vidya", "wma"
"sinwma", "sma", "ssf", "swma", "t3", "tema", "trima", "vidya", "wma"
]
if name is None and source is None:
return _mas
@@ -62,6 +62,7 @@ def ma(name: str = None, source: Series = None, **kwargs) -> Series:
elif name == "rma": return rma(source, **kwargs)
elif name == "sinwma": return sinwma(source, **kwargs)
elif name == "sma": return sma(source, **kwargs)
elif name == "ssf": return ssf(source, **kwargs)
elif name == "swma": return swma(source, **kwargs)
elif name == "t3": return t3(source, **kwargs)
elif name == "tema": return tema(source, **kwargs)
+115
View File
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
from importlib.util import find_spec
from pathlib import Path
from pkg_resources import get_distribution, DistributionNotFound
_dist = get_distribution("pandas_ta")
try:
# Normalize case for Windows systems
_here = Path(_dist.location) / __file__
if not _here.exists():
# not installed, but there is another version that *is*
raise DistributionNotFound
except DistributionNotFound:
__version__ = "Please install this project with setup.py"
version = __version__ = _dist.version
Imports = {
"alphaVantage-api": find_spec("alphaVantageAPI") is not None,
"matplotlib": find_spec("matplotlib") is not None,
"mplfinance": find_spec("mplfinance") is not None,
"numba": find_spec("numba") is not None,
"yaml": find_spec("yaml") is not None,
"scipy": find_spec("scipy") is not None,
"sklearn": find_spec("sklearn") is not None,
"statsmodels": find_spec("statsmodels") is not None,
"stochastic": find_spec("stochastic") is not None,
"talib": find_spec("talib") is not None,
"tqdm": find_spec("tqdm") is not None,
"vectorbt": find_spec("vectorbt") is not None,
"yfinance": find_spec("yfinance") is not None,
"polygon": find_spec("polygon") is not None,
}
# Not ideal and not dynamic but it works.
# Will find a dynamic solution later.
Category = {
# Candles
"candles": [
"cdl_pattern", "cdl_z", "ha"
],
# Cycles
"cycles": ["ebsw", "reflex"],
# Momentum
"momentum": [
"ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo",
"coppock", "cti", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd",
"mom", "pgo", "ppo", "psl", "pvo", "qqe", "roc", "rsi", "rsx", "rvgi",
"slope", "smi", "squeeze", "squeeze_pro", "stc", "stoch", "stochf",
"stochrsi", "td_seq", "trix", "tsi", "uo", "willr"
],
# Overlap
"overlap": [
"alligator", "alma", "dema", "ema", "fwma", "hilo", "hl2", "hlc3",
"hma", "hwma", "ichimoku", "jma", "kama", "linreg", "mcgd", "midpoint",
"midprice", "ohlc4", "pwma", "rma", "sinwma", "sma", "smma", "ssf",
"ssf3", "supertrend", "swma", "t3", "tema", "trima", "vidya", "vwap",
"vwma", "wcp", "wma", "zlma"
],
# Performance
"performance": ["log_return", "percent_return"],
# Statistics
"statistics": [
"entropy", "kurtosis", "mad", "median", "quantile", "skew", "stdev",
"tos_stdevall", "variance", "zscore"
],
# Trend
"trend": [
"adx", "amat", "aroon", "chop", "cksp", "decay", "decreasing", "dpo",
"increasing", "long_run", "psar", "qstick", "short_run", "trendflex", "tsignals",
"ttm_trend", "vhf", "vortex", "xsignals"
],
# Volatility
"volatility": [
"aberration", "accbands", "atr", "bbands", "donchian", "hwc", "kc", "massi",
"natr", "pdist", "rvi", "thermo", "true_range", "ui"
],
# Volume.
# Note: "vp" or "Volume Profile" is excluded since it does not return a Time Series
"volume": [
"ad", "adosc", "aobv", "cmf", "efi", "eom", "kvo", "mfi", "nvi", "obv",
"pvi", "pvol", "pvr", "pvt", "wb_tsv"
],
}
CANDLE_AGG = {
"open": "first",
"high": "max",
"low": "min",
"close": "last",
"volume": "sum"
}
# https://www.worldtimezone.com/markets24.php
EXCHANGE_TZ = {
"NZSX": 12, "ASX": 11,
"TSE": 9, "HKE": 8, "SSE": 8, "SGX": 8,
"NSE": 5.5, "DIFX": 4, "RTS": 3,
"JSE": 2, "FWB": 1, "LSE": 1,
"BMF": -2, "NYSE": -4, "TSX": -4,
"GENR": 0 # Generated Data
}
RATE = {
"DAYS_PER_MONTH": 21,
"MINUTES_PER_HOUR": 60,
"MONTHS_PER_YEAR": 12,
"QUARTERS_PER_YEAR": 4,
"TRADING_DAYS_PER_YEAR": 252, # Keep even
"TRADING_HOURS_PER_DAY": 6.5,
"WEEKS_PER_YEAR": 52,
"YEARLY": 1,
}
+9 -6
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from pandas import Series
from pandas_ta.overlap import sma
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
def ao(high: Series, low: Series, fast: int = None, slow: int = None, offset: int = None, **kwargs) -> Series:
def ao(
high: Series, low: Series, fast: int = None, slow: int = None,
offset: int = None, **kwargs
) -> Series:
"""Awesome Oscillator (AO)
The Awesome Oscillator is an indicator used to measure a security's momentum.
@@ -28,7 +31,7 @@ def ao(high: Series, low: Series, fast: int = None, slow: int = None, offset: in
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
fast = int(fast) if fast and fast > 0 else 5
slow = int(slow) if slow and slow > 0 else 34
if slow < fast:
@@ -40,7 +43,7 @@ def ao(high: Series, low: Series, fast: int = None, slow: int = None, offset: in
if high is None or low is None: return
# Calculate Result
# Calculate
median_price = 0.5 * (high + low)
fast_sma = sma(median_price, fast)
slow_sma = sma(median_price, slow)
@@ -50,13 +53,13 @@ def ao(high: Series, low: Series, fast: int = None, slow: int = None, offset: in
if offset != 0:
ao = ao.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
ao.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
ao.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
ao.name = f"AO_{fast}_{slow}"
ao.category = "momentum"
+12 -9
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.overlap import ma
from pandas_ta.utils import get_offset, tal_ma, verify_series
from pandas import Series
from pandas_ta.ma import ma
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, tal_ma, verify_series
def apo(close: Series, fast: int = None, slow: int = None, mamode: str = None, talib: bool = None,
offset: int = None, **kwargs) -> Series:
def apo(
close: Series, fast: int = None, slow: int = None,
mamode: str = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Absolute Price Oscillator (APO)
The Absolute Price Oscillator is an indicator used to measure a security's
@@ -32,7 +35,7 @@ def apo(close: Series, fast: int = None, slow: int = None, mamode: str = None, t
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
if slow < fast:
@@ -44,7 +47,7 @@ def apo(close: Series, fast: int = None, slow: int = None, mamode: str = None, t
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import APO
apo = APO(close, fast, slow, tal_ma(mamode))
@@ -57,13 +60,13 @@ def apo(close: Series, fast: int = None, slow: int = None, mamode: str = None, t
if offset != 0:
apo = apo.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
apo.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
apo.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
apo.name = f"APO_{fast}_{slow}"
apo.category = "momentum"
+10 -8
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from pandas_ta.overlap import ma
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.ma import ma
from pandas_ta.utils import get_offset, verify_series
def bias(close: Series, length: int = None, mamode: str = None, offset: int = None, **kwargs) -> Series:
def bias(
close: Series, length: int = None, mamode: str = None,
offset: int = None, **kwargs
) -> Series:
"""Bias (BIAS)
Rate of change between the source and a moving average.
@@ -17,7 +20,6 @@ def bias(close: Series, length: int = None, mamode: str = None, offset: int = No
close (pd.Series): Series of 'close's
length (int): The period. Default: 26
mamode (str): See ```help(ta.ma)```. Default: 'sma'
drift (int): The short period. Default: 1
offset (int): How many periods to offset the result. Default: 0
Kwargs:
@@ -27,7 +29,7 @@ def bias(close: Series, length: int = None, mamode: str = None, offset: int = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 26
mamode = mamode if isinstance(mamode, str) else "sma"
close = verify_series(close, length)
@@ -35,7 +37,7 @@ def bias(close: Series, length: int = None, mamode: str = None, offset: int = No
if close is None: return
# Calculate Result
# Calculate
bma = ma(mamode, close, length=length, **kwargs)
bias = (close / bma) - 1
@@ -43,13 +45,13 @@ def bias(close: Series, length: int = None, mamode: str = None, offset: int = No
if offset != 0:
bias = bias.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
bias.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
bias.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
bias.name = f"BIAS_{bma.name}"
bias.category = "momentum"
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.utils import get_offset, non_zero_range, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, non_zero_range, verify_series
def bop(open_: Series, high: Series, low: Series, close: Series, scalar: float = None, talib: bool = None,
offset: int = None, **kwargs) -> Series:
def bop(
open_: Series, high: Series, low: Series, close: Series,
scalar: float = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Balance of Power (BOP)
Balance of Power measure the market strength of buyers against sellers.
@@ -30,7 +33,7 @@ def bop(open_: Series, high: Series, low: Series, close: Series, scalar: float =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
open_ = verify_series(open_)
high = verify_series(high)
low = verify_series(low)
@@ -39,7 +42,7 @@ def bop(open_: Series, high: Series, low: Series, close: Series, scalar: float =
offset = get_offset(offset)
mode_tal = bool(talib) if isinstance(talib, bool) else True
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import BOP
bop = BOP(open_, high, low, close)
@@ -52,13 +55,13 @@ def bop(open_: Series, high: Series, low: Series, close: Series, scalar: float =
if offset != 0:
bop = bop.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
bop.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
bop.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
bop.name = f"BOP"
bop.category = "momentum"
+9 -7
View File
@@ -3,8 +3,11 @@ from pandas import DataFrame, Series
from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series
def brar(open_: Series, high: Series, low: Series, close: Series, length: int = None, scalar: float = None,
drift: int = None, offset: int = None, **kwargs) -> DataFrame:
def brar(
open_: Series, high: Series, low: Series, close: Series,
length: int = None, scalar: float = None, drift: int = None,
offset: int = None, **kwargs
) -> DataFrame:
"""BRAR (BRAR)
BR and AR
@@ -30,7 +33,7 @@ def brar(open_: Series, high: Series, low: Series, close: Series, length: int =
Returns:
pd.DataFrame: ar, br columns.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 26
scalar = float(scalar) if scalar else 100
high_open_range = non_zero_range(high, open_)
@@ -44,7 +47,7 @@ def brar(open_: Series, high: Series, low: Series, close: Series, length: int =
if open_ is None or high is None or low is None or close is None: return
# Calculate Result
# Calculate
hcy = non_zero_range(high, close.shift(drift))
cyl = non_zero_range(close.shift(drift), low)
@@ -62,7 +65,7 @@ def brar(open_: Series, high: Series, low: Series, close: Series, length: int =
ar = ar.shift(offset)
br = ar.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
ar.fillna(kwargs["fillna"], inplace=True)
br.fillna(kwargs["fillna"], inplace=True)
@@ -70,13 +73,12 @@ def brar(open_: Series, high: Series, low: Series, close: Series, length: int =
ar.fillna(method=kwargs["fill_method"], inplace=True)
br.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = f"_{length}"
ar.name = f"AR{_props}"
br.name = f"BR{_props}"
ar.category = br.category = "momentum"
# Prepare DataFrame to return
brardf = DataFrame({ar.name: ar, br.name: br})
brardf.name = f"BRAR{_props}"
brardf.category = "momentum"
+13 -10
View File
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.overlap import hlc3, sma
from pandas_ta.statistics.mad import mad
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.overlap import hlc3, sma
from pandas_ta.statistics import mad
from pandas_ta.utils import get_offset, verify_series
def cci(high: Series, low: Series, close: Series, length: int = None, c: float = None,
talib: bool = None, offset: int = None, **kwargs) -> Series:
def cci(
high: Series, low: Series, close: Series, length: int = None,
c: float = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Commodity Channel Index (CCI)
Commodity Channel Index is a momentum oscillator used to primarily identify
@@ -33,7 +36,7 @@ def cci(high: Series, low: Series, close: Series, length: int = None, c: float =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 14
c = float(c) if c and c > 0 else 0.015
high = verify_series(high, length)
@@ -44,7 +47,7 @@ def cci(high: Series, low: Series, close: Series, length: int = None, c: float =
if high is None or low is None or close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import CCI
cci = CCI(high, low, close, length)
@@ -60,13 +63,13 @@ def cci(high: Series, low: Series, close: Series, length: int = None, c: float =
if offset != 0:
cci = cci.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
cci.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
cci.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
cci.name = f"CCI_{length}_{c}"
cci.category = "momentum"
+10 -6
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from pandas import Series
from pandas_ta.overlap import linreg
from pandas_ta.utils import get_drift, get_offset, verify_series
from pandas import Series
def cfo(close: Series, length: int = None, scalar: float = None, drift: int = None, offset: int = None,
**kwargs) -> Series:
def cfo(
close: Series, length: int = None,
scalar: float = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Chande Forcast Oscillator (CFO)
The Forecast Oscillator calculates the percentage difference between the actual
@@ -28,7 +31,7 @@ def cfo(close: Series, length: int = None, scalar: float = None, drift: int = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 9
scalar = float(scalar) if scalar else 100
close = verify_series(close, length)
@@ -37,6 +40,7 @@ def cfo(close: Series, length: int = None, scalar: float = None, drift: int = No
if close is None: return
# Calculate
# Finding linear regression of Series
cfo = scalar * (close - linreg(close, length=length, tsf=True))
cfo /= close
@@ -45,13 +49,13 @@ def cfo(close: Series, length: int = None, scalar: float = None, drift: int = No
if offset != 0:
cfo = cfo.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
cfo.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
cfo.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
cfo.name = f"CFO_{length}"
cfo.category = "momentum"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series, weights
from pandas import Series
from pandas_ta.utils import get_offset, verify_series, weights
def cg(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def cg(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Center of Gravity (CG)
The Center of Gravity Indicator by John Ehlers attempts to identify turning
@@ -24,14 +27,14 @@ def cg(close: Series, length: int = None, offset: int = None, **kwargs) -> Serie
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
offset = get_offset(offset)
if close is None: return
# Calculate Result
# Calculate
coefficients = [length - i for i in range(0, length)]
numerator = -close.rolling(length).apply(weights(coefficients), raw=True)
cg = numerator / close.rolling(length).sum()
@@ -40,13 +43,13 @@ def cg(close: Series, length: int = None, offset: int = None, **kwargs) -> Serie
if offset != 0:
cg = cg.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
cg.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
cg.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
cg.name = f"CG_{length}"
cg.category = "momentum"
+11 -8
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.overlap import rma
from pandas_ta.utils import get_drift, get_offset, verify_series
from pandas import Series
def cmo(close: Series, length: int = None, scalar: float = None, talib: bool = None, drift: int = None,
offset: int = None, **kwargs) -> Series:
def cmo(
close: Series, length: int = None, scalar: float = None,
talib: bool = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Chande Momentum Oscillator (CMO)
Attempts to capture the momentum of an asset with overbought at 50 and
@@ -33,7 +36,7 @@ def cmo(close: Series, length: int = None, scalar: float = None, talib: bool = N
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 14
scalar = float(scalar) if scalar else 100
close = verify_series(close, length)
@@ -43,7 +46,7 @@ def cmo(close: Series, length: int = None, scalar: float = None, talib: bool = N
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import CMO
cmo = CMO(close, length)
@@ -65,13 +68,13 @@ def cmo(close: Series, length: int = None, scalar: float = None, talib: bool = N
if offset != 0:
cmo = cmo.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
cmo.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
cmo.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
cmo.name = f"CMO_{length}"
cmo.category = "momentum"
+10 -8
View File
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
from .roc import roc
from pandas import Series
from pandas_ta.overlap import wma
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from .roc import roc
def coppock(close: Series, length: int = None, fast: int = None, slow: int = None, offset: int = None,
**kwargs) -> Series:
def coppock(
close: Series, length: int = None, fast: int = None, slow: int = None,
offset: int = None, **kwargs
) -> Series:
"""Coppock Curve (COPC)
Coppock Curve (originally called the "Trendex Model") is a momentum indicator
@@ -32,7 +34,7 @@ def coppock(close: Series, length: int = None, fast: int = None, slow: int = Non
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
fast = int(fast) if fast and fast > 0 else 11
slow = int(slow) if slow and slow > 0 else 14
@@ -41,7 +43,7 @@ def coppock(close: Series, length: int = None, fast: int = None, slow: int = Non
if close is None: return
# Calculate Result
# Calculate
total_roc = roc(close, fast) + roc(close, slow)
coppock = wma(total_roc, length)
@@ -49,13 +51,13 @@ def coppock(close: Series, length: int = None, fast: int = None, slow: int = Non
if offset != 0:
coppock = coppock.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
coppock.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
coppock.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
coppock.name = f"COPC_{fast}_{slow}_{length}"
coppock.category = "momentum"
+9 -2
View File
@@ -4,7 +4,10 @@ from pandas_ta.overlap import linreg
from pandas_ta.utils import get_offset, verify_series
def cti(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def cti(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Correlation Trend Indicator (CTI)
The Correlation Trend Indicator is an oscillator created by John Ehler in 2020.
@@ -24,24 +27,28 @@ def cti(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
Returns:
pd.Series: Series of the CTI values for the given period.
"""
# Validate
length = int(length) if length and length > 0 else 12
close = verify_series(close, length)
offset = get_offset(offset)
if close is None: return
# Calculate
cti = linreg(close, length=length, r=True)
# Offset
if offset != 0:
cti = cti.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
cti.fillna(method=kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
cti.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Category
cti.name = f"CTI_{length}"
cti.category = "momentum"
return cti
+19 -12
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from pandas_ta import Imports
from pandas_ta.overlap import ma
from pandas_ta.ma import ma
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series, get_drift, zero
def dm(high: Series, low: Series, length: int = None, mamode: str = None, talib: bool = None, drift: int = None,
offset: int = None, **kwargs) -> DataFrame:
def dm(
high: Series, low: Series, length: int = None,
mamode: str = None, talib: bool = None, drift: int = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Directional Movement (DM)
The Directional Movement was developed by J. Welles Wilder in 1978 attempts to
@@ -33,7 +36,7 @@ def dm(high: Series, low: Series, length: int = None, mamode: str = None, talib:
Returns:
pd.DataFrame: DMP (+DM) and DMN (-DM) columns.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 14
mamode = mamode.lower() if mamode and isinstance(mamode, str) else "rma"
high = verify_series(high)
@@ -68,16 +71,20 @@ def dm(high: Series, low: Series, length: int = None, mamode: str = None, talib:
pos = pos.shift(offset)
neg = neg.shift(offset)
# Fill
if "fillna" in kwargs:
pos.fillna(kwargs["fillna"], inplace=True)
neg.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
pos.fillna(method=kwargs["fill_method"], inplace=True)
neg.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Category
_params = f"_{length}"
data = {
f"DMP{_params}": pos,
f"DMN{_params}": neg,
}
data = {f"DMP{_params}": pos, f"DMN{_params}": neg,}
dmdf = DataFrame(data)
# print(dmdf.head(20))
# print()
dmdf.name = f"DM{_params}"
dmdf.category = "trend"
dmdf.category = "momentum"
return dmdf
+8 -5
View File
@@ -3,7 +3,10 @@ from pandas import DataFrame, concat, Series
from pandas_ta.utils import get_drift, get_offset, verify_series, signals
def er(close: Series, length: int = None, drift: int = None, offset: int = None, **kwargs) -> Series:
def er(
close: Series, length: int = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Efficiency Ratio (ER)
The Efficiency Ratio was invented by Perry J. Kaufman and presented in his book "New Trading Systems and Methods". It is designed to account for market noise or volatility.
@@ -25,7 +28,7 @@ def er(close: Series, length: int = None, drift: int = None, offset: int = None,
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
offset = get_offset(offset)
@@ -33,7 +36,7 @@ def er(close: Series, length: int = None, drift: int = None, offset: int = None,
if close is None: return
# Calculate Result
# Calculate
abs_diff = close.diff(length).abs()
abs_volatility = close.diff(drift).abs()
@@ -44,13 +47,13 @@ def er(close: Series, length: int = None, drift: int = None, offset: int = None,
if offset != 0:
er = er.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
er.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
er.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
er.name = f"ER_{length}"
er.category = "momentum"
+8 -6
View File
@@ -4,7 +4,10 @@ from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series
def eri(high: Series, low: Series, close: Series, length: int = None, offset: int = None, **kwargs) -> DataFrame:
def eri(
high: Series, low: Series, close: Series, length: int = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Elder Ray Index (ERI)
Elder's Bulls Ray Index contains his Bull and Bear Powers. Which are useful ways
@@ -34,7 +37,7 @@ def eri(high: Series, low: Series, close: Series, length: int = None, offset: in
Returns:
pd.DataFrame: bull power and bear power columns.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 13
high = verify_series(high, length)
low = verify_series(low, length)
@@ -43,7 +46,7 @@ def eri(high: Series, low: Series, close: Series, length: int = None, offset: in
if high is None or low is None or close is None: return
# Calculate Result
# Calculate
ema_ = ema(close, length)
bull = high - ema_
bear = low - ema_
@@ -53,7 +56,7 @@ def eri(high: Series, low: Series, close: Series, length: int = None, offset: in
bull = bull.shift(offset)
bear = bear.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
bull.fillna(kwargs["fillna"], inplace=True)
bear.fillna(kwargs["fillna"], inplace=True)
@@ -61,12 +64,11 @@ def eri(high: Series, low: Series, close: Series, length: int = None, offset: in
bull.fillna(method=kwargs["fill_method"], inplace=True)
bear.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
bull.name = f"BULLP_{length}"
bear.name = f"BEARP_{length}"
bull.category = bear.category = "momentum"
# Prepare DataFrame to return
data = {bull.name: bull, bear.name: bear}
df = DataFrame(data)
df.name = f"ERI_{length}"
+11 -11
View File
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
from numpy import log as nplog
from numpy import nan as npNaN
from numpy import log, nan
from pandas import DataFrame, Series
from pandas_ta.overlap import hl2
from pandas_ta.utils import get_offset, high_low_range, verify_series
def fisher(high: Series, low: Series, length: int = None, signal: int = None, offset: int = None,
**kwargs) -> Series:
def fisher(
high: Series, low: Series, length: int = None, signal: int = None,
offset: int = None, **kwargs
) -> Series:
"""Fisher Transform (FISHT)
Attempts to identify significant price reversals by normalizing prices over a
@@ -31,7 +32,7 @@ def fisher(high: Series, low: Series, length: int = None, signal: int = None, of
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 9
signal = int(signal) if signal and signal > 0 else 1
_length = max(length, signal)
@@ -41,7 +42,7 @@ def fisher(high: Series, low: Series, length: int = None, signal: int = None, of
if high is None or low is None: return
# Calculate Result
# Calculate
hl2_ = hl2(high, low)
highest_hl2 = hl2_.rolling(length).max()
lowest_hl2 = hl2_.rolling(length).min()
@@ -53,12 +54,12 @@ def fisher(high: Series, low: Series, length: int = None, signal: int = None, of
v = 0
m = high.size
result = [npNaN for _ in range(0, length - 1)] + [0]
result = [nan for _ in range(0, length - 1)] + [0]
for i in range(length, m):
v = 0.66 * position.iloc[i] + 0.67 * v
if v < -0.99: v = -0.999
if v > 0.99: v = 0.999
result.append(0.5 * (nplog((1 + v) / (1 - v)) + result[i - 1]))
result.append(0.5 * (log((1 + v) / (1 - v)) + result[i - 1]))
fisher = Series(result, index=high.index)
signalma = fisher.shift(signal)
@@ -67,7 +68,7 @@ def fisher(high: Series, low: Series, length: int = None, signal: int = None, of
fisher = fisher.shift(offset)
signalma = signalma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
fisher.fillna(kwargs["fillna"], inplace=True)
signalma.fillna(kwargs["fillna"], inplace=True)
@@ -75,13 +76,12 @@ def fisher(high: Series, low: Series, length: int = None, signal: int = None, of
fisher.fillna(method=kwargs["fill_method"], inplace=True)
signalma.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = f"_{length}_{signal}"
fisher.name = f"FISHERT{_props}"
signalma.name = f"FISHERTs{_props}"
fisher.category = signalma.category = "momentum"
# Prepare DataFrame to return
data = {fisher.name: fisher, signalma.name: signalma}
df = DataFrame(data)
df.name = f"FISHERT{_props}"
+14 -10
View File
@@ -1,13 +1,17 @@
# -*- coding: utf-8 -*-
from pandas_ta.overlap import linreg
from pandas_ta.volatility import rvi
from pandas_ta.utils import get_drift, get_offset, verify_series
from pandas import Series
from pandas_ta.overlap import linreg
from pandas_ta.utils import get_drift, get_offset, verify_series
from pandas_ta.volatility import rvi
def inertia(close: Series, high: Series, low: Series, length: int = None, rvi_length: int = None, scalar: float = None,
refined: bool = None, thirds: bool = None, mamode: str = None, drift: int = None, offset: int = None,
**kwargs) -> Series:
def inertia(
close: Series, high: Series = None, low: Series = None,
length: int = None, rvi_length: int = None, scalar: float = None,
refined: bool = None, thirds: bool = None,
drift: int = None, mamode: str = None,
offset: int = None, **kwargs
) -> Series:
"""Inertia (INERTIA)
Inertia was developed by Donald Dorsey and was introduced his article
@@ -38,7 +42,7 @@ def inertia(close: Series, high: Series, low: Series, length: int = None, rvi_le
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 20
rvi_length = int(rvi_length) if rvi_length and rvi_length > 0 else 14
scalar = float(scalar) if scalar and scalar > 0 else 100
@@ -57,7 +61,7 @@ def inertia(close: Series, high: Series, low: Series, length: int = None, rvi_le
low = verify_series(low, _length)
if high is None or low is None: return
# Calculate Result
# Calculate
if refined:
_mode, rvi_ = "r", rvi(close, high=high, low=low, length=rvi_length, scalar=scalar, refined=refined, mamode=mamode)
elif thirds:
@@ -71,13 +75,13 @@ def inertia(close: Series, high: Series, low: Series, length: int = None, rvi_le
if offset != 0:
inertia = inertia.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
inertia.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
inertia.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
_props = f"_{length}_{rvi_length}"
inertia.name = f"INERTIA{_mode}{_props}"
inertia.category = "momentum"
+9 -7
View File
@@ -3,8 +3,11 @@ from pandas import DataFrame, Series
from pandas_ta.utils import get_offset, non_zero_range, rma_pandas, verify_series
def kdj(high: Series, low: Series, close: Series, length: int = None, signal: int = None, offset: int = None,
**kwargs) -> Series:
def kdj(
high: Series, low: Series, close: Series,
length: int = None, signal: int = None,
offset: int = None, **kwargs
) -> Series:
"""KDJ (KDJ)
The KDJ indicator is actually a derived form of the Slow
@@ -32,7 +35,7 @@ def kdj(high: Series, low: Series, close: Series, length: int = None, signal: in
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 9
signal = int(signal) if signal and signal > 0 else 3
_length = max(length, signal)
@@ -43,7 +46,7 @@ def kdj(high: Series, low: Series, close: Series, length: int = None, signal: in
if high is None or low is None or close is None: return
# Calculate Result
# Calculate
highest_high = high.rolling(length).max()
lowest_low = low.rolling(length).min()
@@ -59,7 +62,7 @@ def kdj(high: Series, low: Series, close: Series, length: int = None, signal: in
d = d.shift(offset)
j = j.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
k.fillna(kwargs["fillna"], inplace=True)
d.fillna(kwargs["fillna"], inplace=True)
@@ -69,14 +72,13 @@ def kdj(high: Series, low: Series, close: Series, length: int = None, signal: in
d.fillna(method=kwargs["fill_method"], inplace=True)
j.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_params = f"_{length}_{signal}"
k.name = f"K{_params}"
d.name = f"D{_params}"
j.name = f"J{_params}"
k.category = d.category = j.category = "momentum"
# Prepare DataFrame to return
kdjdf = DataFrame({k.name: k, d.name: d, j.name: j})
kdjdf.name = f"KDJ{_params}"
kdjdf.category = "momentum"
+12 -9
View File
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from .roc import roc
from pandas_ta.utils import get_drift, get_offset, verify_series
from .roc import roc
def kst(close: Series, roc1: int = None, roc2: int = None, roc3: int = None, roc4: int = None, sma1: int = None,
sma2: int = None, sma3: int = None, sma4: int = None, signal: int = None, drift: int = None,
offset: int = None, **kwargs) -> DataFrame:
def kst(
close: Series, signal: int = None,
roc1: int = None, roc2: int = None, roc3: int = None, roc4: int = None,
sma1: int = None, sma2: int = None, sma3: int = None, sma4: int = None,
drift: int = None,
offset: int = None, **kwargs
) -> DataFrame:
"""'Know Sure Thing' (KST)
The 'Know Sure Thing' is a momentum based oscillator and based on ROC.
@@ -36,7 +40,7 @@ def kst(close: Series, roc1: int = None, roc2: int = None, roc3: int = None, roc
Returns:
pd.DataFrame: kst and kst_signal columns
"""
# Validate arguments
# Validate
roc1 = int(roc1) if roc1 and roc1 > 0 else 10
roc2 = int(roc2) if roc2 and roc2 > 0 else 15
roc3 = int(roc3) if roc3 and roc3 > 0 else 20
@@ -55,7 +59,7 @@ def kst(close: Series, roc1: int = None, roc2: int = None, roc3: int = None, roc
if close is None: return
# Calculate Result
# Calculate
rocma1 = roc(close, roc1).rolling(sma1).mean()
rocma2 = roc(close, roc2).rolling(sma2).mean()
rocma3 = roc(close, roc3).rolling(sma3).mean()
@@ -69,7 +73,7 @@ def kst(close: Series, roc1: int = None, roc2: int = None, roc3: int = None, roc
kst = kst.shift(offset)
kst_signal = kst_signal.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
kst.fillna(kwargs["fillna"], inplace=True)
kst_signal.fillna(kwargs["fillna"], inplace=True)
@@ -77,12 +81,11 @@ def kst(close: Series, roc1: int = None, roc2: int = None, roc3: int = None, roc
kst.fillna(method=kwargs["fill_method"], inplace=True)
kst_signal.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
kst.name = f"KST_{roc1}_{roc2}_{roc3}_{roc4}_{sma1}_{sma2}_{sma3}_{sma4}"
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}"
+9 -8
View File
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
from pandas import concat, DataFrame, Series
from pandas_ta import Imports
from pandas_ta.maps import Imports
from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series, signals
def macd(close: Series, fast: int = None, slow: int = None, signal: int = None, talib: bool = None,
offset: int = None, **kwargs) -> DataFrame:
def macd(
close: Series, fast: int = None, slow: int = None, signal: int = None,
talib: bool = None, offset: int = None, **kwargs
) -> DataFrame:
"""Moving Average Convergence Divergence (MACD)
The MACD is a popular indicator to that is used to identify a security's trend.
@@ -36,7 +38,7 @@ def macd(close: Series, fast: int = None, slow: int = None, signal: int = None,
Returns:
pd.DataFrame: macd, histogram, signal columns.
"""
# Validate arguments
# Validate
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
signal = int(signal) if signal and signal > 0 else 9
@@ -50,7 +52,7 @@ def macd(close: Series, fast: int = None, slow: int = None, signal: int = None,
as_mode = kwargs.setdefault("asmode", False)
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import MACD
macd, signalma, histogram = MACD(close, fast, slow, signal)
@@ -73,7 +75,7 @@ def macd(close: Series, fast: int = None, slow: int = None, signal: int = None,
histogram = histogram.shift(offset)
signalma = signalma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
macd.fillna(kwargs["fillna"], inplace=True)
histogram.fillna(kwargs["fillna"], inplace=True)
@@ -83,7 +85,7 @@ def macd(close: Series, fast: int = None, slow: int = None, signal: int = None,
histogram.fillna(method=kwargs["fill_method"], inplace=True)
signalma.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_asmode = "AS" if as_mode else ""
_props = f"_{fast}_{slow}_{signal}"
macd.name = f"MACD{_asmode}{_props}"
@@ -91,7 +93,6 @@ def macd(close: Series, fast: int = None, slow: int = None, signal: int = None,
signalma.name = f"MACD{_asmode}s{_props}"
macd.category = histogram.category = signalma.category = "momentum"
# Prepare DataFrame to return
data = {macd.name: macd, histogram.name: histogram, signalma.name: signalma}
df = DataFrame(data)
df.name = f"MACD{_asmode}{_props}"
+10 -7
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
def mom(close: Series, length: int = None, talib: bool = None, offset: int = None, **kwargs) -> Series:
def mom(
close: Series, length: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Momentum (MOM)
Momentum is an indicator used to measure a security's speed (or strength) of
@@ -27,7 +30,7 @@ def mom(close: Series, length: int = None, talib: bool = None, offset: int = Non
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
offset = get_offset(offset)
@@ -35,7 +38,7 @@ def mom(close: Series, length: int = None, talib: bool = None, offset: int = Non
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import MOM
mom = MOM(close, length)
@@ -46,13 +49,13 @@ def mom(close: Series, length: int = None, talib: bool = None, offset: int = Non
if offset != 0:
mom = mom.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
mom.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
mom.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
mom.name = f"MOM_{length}"
mom.category = "momentum"
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from pandas_ta.overlap import ema, sma
from pandas_ta.volatility import atr
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.overlap import ema, sma
from pandas_ta.utils import get_offset, verify_series
from pandas_ta.volatility import atr
def pgo(high: Series, low: Series, close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def pgo(
high: Series, low: Series, close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Pretty Good Oscillator (PGO)
The Pretty Good Oscillator indicator was created by Mark Johnson to measure the distance of the current close from its N-day Simple Moving Average, expressed in terms of an average true range over a similar period. Johnson's approach was to
@@ -29,7 +32,7 @@ def pgo(high: Series, low: Series, close: Series, length: int = None, offset: in
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 14
high = verify_series(high, length)
low = verify_series(low, length)
@@ -38,7 +41,7 @@ def pgo(high: Series, low: Series, close: Series, length: int = None, offset: in
if high is None or low is None or close is None: return
# Calculate Result
# Calculate
pgo = close - sma(close, length)
pgo /= ema(atr(high, low, close, length), length)
@@ -46,13 +49,13 @@ def pgo(high: Series, low: Series, close: Series, length: int = None, offset: in
if offset != 0:
pgo = pgo.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
pgo.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
pgo.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
pgo.name = f"PGO_{length}"
pgo.category = "momentum"
+11 -10
View File
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from pandas_ta import Imports
from pandas_ta.overlap import ma
from pandas_ta.ma import ma
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, tal_ma, verify_series
def ppo(close: Series, fast: int = None, slow: int = None, signal: int = None, scalar: float = None,
mamode: str = None, talib: bool = None, offset: int = None, **kwargs) -> DataFrame:
def ppo(
close: Series, fast: int = None, slow: int = None, signal: int = None,
scalar: float = None, mamode: str = None, talib: bool = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Percentage Price Oscillator (PPO)
The Percentage Price Oscillator is similar to MACD in measuring momentum.
@@ -33,7 +35,7 @@ def ppo(close: Series, fast: int = None, slow: int = None, signal: int = None, s
Returns:
pd.DataFrame: ppo, histogram, signal columns
"""
# Validate Arguments
# Validate
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
signal = int(signal) if signal and signal > 0 else 9
@@ -47,7 +49,7 @@ def ppo(close: Series, fast: int = None, slow: int = None, signal: int = None, s
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import PPO
ppo = PPO(close, fast, slow, tal_ma(mamode))
@@ -66,7 +68,7 @@ def ppo(close: Series, fast: int = None, slow: int = None, signal: int = None, s
histogram = histogram.shift(offset)
signalma = signalma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
ppo.fillna(kwargs["fillna"], inplace=True)
histogram.fillna(kwargs["fillna"], inplace=True)
@@ -76,14 +78,13 @@ def ppo(close: Series, fast: int = None, slow: int = None, signal: int = None, s
histogram.fillna(method=kwargs["fill_method"], inplace=True)
signalma.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = f"_{fast}_{slow}_{signal}"
ppo.name = f"PPO{_props}"
histogram.name = f"PPOh{_props}"
signalma.name = f"PPOs{_props}"
ppo.category = histogram.category = signalma.category = "momentum"
# Prepare DataFrame to return
data = {ppo.name: ppo, histogram.name: histogram, signalma.name: signalma}
df = DataFrame(data)
df.name = f"PPO{_props}"
+14 -11
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from numpy import sign as npSign
from pandas_ta.utils import get_drift, get_offset, verify_series
from numpy import sign
from pandas import Series
from pandas_ta.utils import get_drift, get_offset, verify_series
def psl(close: Series, open_: Series = None, length: int = None, scalar: float = None, drift: int = None,
offset: int = None, **kwargs) -> Series:
def psl(
close: Series, open_: Series = None,
length: int = None, scalar: float = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Psychological Line (PSL)
The Psychological Line is an oscillator-type indicator that compares the
@@ -31,7 +34,7 @@ def psl(close: Series, open_: Series = None, length: int = None, scalar: float =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 12
scalar = float(scalar) if scalar and scalar > 0 else 100
close = verify_series(close, length)
@@ -40,15 +43,15 @@ def psl(close: Series, open_: Series = None, length: int = None, scalar: float =
if close is None: return
# Calculate Result
# Calculate
if open_ is not None:
open_ = verify_series(open_)
diff = npSign(close - open_)
diff = sign(close - open_)
else:
diff = npSign(close.diff(drift))
diff = sign(close.diff(drift))
diff.fillna(0, inplace=True)
diff[diff <= 0] = 0 # Zero negative values
diff[diff <= 0] = 0 # Set negative values to zero
psl = scalar * diff.rolling(length).sum()
psl /= length
@@ -57,13 +60,13 @@ def psl(close: Series, open_: Series = None, length: int = None, scalar: float =
if offset != 0:
psl = psl.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
psl.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
psl.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = f"_{length}"
psl.name = f"PSL{_props}"
psl.category = "momentum"
+9 -7
View File
@@ -4,8 +4,11 @@ from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series
def pvo(volume: Series, fast: int = None, slow: int = None, signal: int = None, scalar: float = None,
offset: int = None, **kwargs) -> DataFrame:
def pvo(
volume: Series, fast: int = None, slow: int = None, signal: int = None,
scalar: float = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Percentage Volume Oscillator (PVO)
Percentage Volume Oscillator is a Momentum Oscillator for Volume.
@@ -28,7 +31,7 @@ def pvo(volume: Series, fast: int = None, slow: int = None, signal: int = None,
Returns:
pd.DataFrame: pvo, histogram, signal columns.
"""
# Validate Arguments
# Validate
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
signal = int(signal) if signal and signal > 0 else 9
@@ -40,7 +43,7 @@ def pvo(volume: Series, fast: int = None, slow: int = None, signal: int = None,
if volume is None: return
# Calculate Result
# Calculate
fastma = ema(volume, length=fast)
slowma = ema(volume, length=slow)
pvo = scalar * (fastma - slowma)
@@ -55,7 +58,7 @@ def pvo(volume: Series, fast: int = None, slow: int = None, signal: int = None,
histogram = histogram.shift(offset)
signalma = signalma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
pvo.fillna(kwargs["fillna"], inplace=True)
histogram.fillna(kwargs["fillna"], inplace=True)
@@ -65,14 +68,13 @@ def pvo(volume: Series, fast: int = None, slow: int = None, signal: int = None,
histogram.fillna(method=kwargs["fill_method"], inplace=True)
signalma.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = f"_{fast}_{slow}_{signal}"
pvo.name = f"PVO{_props}"
histogram.name = f"PVOh{_props}"
signalma.name = f"PVOs{_props}"
pvo.category = histogram.category = signalma.category = "momentum"
#
data = {pvo.name: pvo, histogram.name: histogram, signalma.name: signalma}
df = DataFrame(data)
df.name = pvo.name
+21 -18
View File
@@ -1,16 +1,17 @@
# -*- coding: utf-8 -*-
from numpy import maximum as npMaximum
from numpy import minimum as npMinimum
from numpy import nan as npNaN
from numpy import isnan, maximum, minimum, nan
from pandas import DataFrame, Series
from .rsi import rsi
from pandas_ta.overlap import ma
from pandas_ta.ma import ma
from pandas_ta.utils import get_drift, get_offset, verify_series
from .rsi import rsi
def qqe(close: Series, length: int = None, smooth: int = None, factor: float = None, mamode: str = None,
drift: int = None, offset: int = None, **kwargs) -> DataFrame:
def qqe(
close: Series, length: int = None,
smooth: int = None, factor: float = None,
mamode: str = None, drift: int = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Quantitative Qualitative Estimation (QQE)
The Quantitative Qualitative Estimation (QQE) is similar to SuperTrend but uses a Smoothed RSI with an upper and lower bands. The band width is a combination of a one period True Range of the Smoothed RSI which is double smoothed using Wilder's smoothing length (2 * rsiLength - 1) and multiplied by the default factor of 4.236. A Long trend is determined when the Smoothed RSI crosses the previous upperband and a Short trend when the Smoothed RSI crosses the previous lowerband.
@@ -38,30 +39,33 @@ def qqe(close: Series, length: int = None, smooth: int = None, factor: float = N
Returns:
pd.DataFrame: QQE, RSI_MA (basis), QQEl (long), and QQEs (short) columns.
"""
# Validate arguments
# Validate
length = int(length) if isinstance(length, int) and length > 0 else 14
smooth = int(smooth) if isinstance(smooth, int) and smooth > 0 else 5
factor = float(factor) if isinstance(factor, float) and factor else 4.236
wilders_length = 2 * length - 1
mamode = mamode if isinstance(mamode, str) else "ema"
close = verify_series(close, max(length, smooth, wilders_length))
close = verify_series(close, smooth + wilders_length)
drift = get_drift(drift)
offset = get_offset(offset)
if close is None: return
# Calculate Result
# Calculate
rsi_ = rsi(close, length)
_mode = mamode.lower()[0] if mamode != "ema" else ""
rsi_ma = ma(mamode, rsi_, length=smooth)
# RSI MA True Range
rsi_ma_tr = rsi_ma.diff(drift).abs()
if all(isnan(rsi_ma_tr)): return
# Double Smooth the RSI MA True Range using Wilder's Length with a default
# width of 4.236.
smoothed_rsi_tr_ma = ma("ema", rsi_ma_tr, length=wilders_length)
if all(isnan(smoothed_rsi_tr_ma)): return # Emergency Break
dar = factor * ma("ema", smoothed_rsi_tr_ma, length=wilders_length)
if all(isnan(dar)): return # Emergency Break
# Create the Upper and Lower Bands around RSI MA.
upperband = rsi_ma + dar
@@ -72,8 +76,8 @@ def qqe(close: Series, length: int = None, smooth: int = None, factor: float = N
short = Series(0, index=close.index)
trend = Series(1, index=close.index)
qqe = Series(rsi_ma.iloc[0], index=close.index)
qqe_long = Series(npNaN, index=close.index)
qqe_short = Series(npNaN, index=close.index)
qqe_long = Series(nan, index=close.index)
qqe_short = Series(nan, index=close.index)
for i in range(1, m):
c_rsi, p_rsi = rsi_ma.iloc[i], rsi_ma.iloc[i - 1]
@@ -82,13 +86,13 @@ def qqe(close: Series, length: int = None, smooth: int = None, factor: float = N
# Long Line
if p_rsi > c_long and c_rsi > c_long:
long.iloc[i] = npMaximum(c_long, lowerband.iloc[i])
long.iloc[i] = maximum(c_long, lowerband.iloc[i])
else:
long.iloc[i] = lowerband.iloc[i]
# Short Line
if p_rsi < c_short and c_rsi < c_short:
short.iloc[i] = npMinimum(c_short, upperband.iloc[i])
short.iloc[i] = minimum(c_short, upperband.iloc[i])
else:
short.iloc[i] = upperband.iloc[i]
@@ -115,7 +119,7 @@ def qqe(close: Series, length: int = None, smooth: int = None, factor: float = N
long = long.shift(offset)
short = short.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
rsi_ma.fillna(kwargs["fillna"], inplace=True)
qqe.fillna(kwargs["fillna"], inplace=True)
@@ -127,7 +131,7 @@ def qqe(close: Series, length: int = None, smooth: int = None, factor: float = N
qqe_long.fillna(method=kwargs["fill_method"], inplace=True)
qqe_short.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = f"{_mode}_{length}_{smooth}_{factor}"
qqe.name = f"QQE{_props}"
rsi_ma.name = f"QQE{_props}_RSI{_mode.upper()}MA"
@@ -136,7 +140,6 @@ def qqe(close: Series, length: int = None, smooth: int = None, factor: float = N
qqe.category = rsi_ma.category = "momentum"
qqe_long.category = qqe_short.category = qqe.category
# Prepare DataFrame to return
data = {
qqe.name: qqe, rsi_ma.name: rsi_ma,
# long.name: long, short.name: short
+12 -9
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from .mom import mom
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
from .mom import mom
def roc(close: Series, length: int = None, scalar: float = None, talib: bool = None, offset: int = None,
**kwargs) -> Series:
def roc(
close: Series, length: int = None,
scalar: float = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Rate of Change (ROC)
Rate of Change is an indicator is also referred to as Momentum (yeah, confusingly).
@@ -31,7 +34,7 @@ def roc(close: Series, length: int = None, scalar: float = None, talib: bool = N
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
scalar = float(scalar) if scalar and scalar > 0 else 100
close = verify_series(close, length)
@@ -40,7 +43,7 @@ def roc(close: Series, length: int = None, scalar: float = None, talib: bool = N
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import ROC
roc = ROC(close, length)
@@ -51,13 +54,13 @@ def roc(close: Series, length: int = None, scalar: float = None, talib: bool = N
if offset != 0:
roc = roc.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
roc.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
roc.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
roc.name = f"ROC_{length}"
roc.category = "momentum"
+10 -7
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, concat, Series
from pandas_ta import Imports
from pandas_ta.maps import Imports
from pandas_ta.overlap import rma
from pandas_ta.utils import get_drift, get_offset, verify_series, signals
def rsi(close: Series, length: int = None, scalar: float = None, talib: bool = None, drift: int = None,
offset: int = None, **kwargs) -> Series:
def rsi(
close: Series, length: int = None, scalar: float = None,
talib: bool = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Relative Strength Index (RSI)
The Relative Strength Index is popular momentum oscillator used to measure the
@@ -31,7 +34,7 @@ def rsi(close: Series, length: int = None, scalar: float = None, talib: bool = N
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 14
scalar = float(scalar) if scalar else 100
close = verify_series(close, length)
@@ -41,7 +44,7 @@ def rsi(close: Series, length: int = None, scalar: float = None, talib: bool = N
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import RSI
rsi = RSI(close, length)
@@ -61,13 +64,13 @@ def rsi(close: Series, length: int = None, scalar: float = None, talib: bool = N
if offset != 0:
rsi = rsi.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
rsi.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
rsi.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
rsi.name = f"RSI_{length}"
rsi.category = "momentum"
+11 -9
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from numpy import nan as npNaN
from numpy import nan
from pandas import concat, DataFrame, Series
from pandas_ta.utils import get_drift, get_offset, verify_series, signals
def rsx(close: Series, length: int = None, drift: int = None, offset: int = None, **kwargs) -> Series:
def rsx(
close: Series, length: int = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Relative Strength Xtra (rsx)
The Relative Strength Xtra is based on the popular RSI indicator and inspired
@@ -30,7 +33,7 @@ def rsx(close: Series, length: int = None, drift: int = None, offset: int = None
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 14
close = verify_series(close, length)
drift = get_drift(drift)
@@ -38,7 +41,8 @@ def rsx(close: Series, length: int = None, drift: int = None, offset: int = None
if close is None: return
# variables
# Calculate
m = close.size
vC, v1C = 0, 0
v4, v8, v10, v14, v18, v20 = 0, 0, 0, 0, 0, 0
@@ -46,9 +50,7 @@ def rsx(close: Series, length: int = None, drift: int = None, offset: int = None
f40, f48, f50, f58, f60, f68, f70, f78 = 0, 0, 0, 0, 0, 0, 0, 0
f80, f88, f90 = 0, 0, 0
# Calculate Result
m = close.size
result = [npNaN for _ in range(0, length - 1)] + [0]
result = [nan for _ in range(0, length - 1)] + [0]
for i in range(length, m):
if f90 == 0:
f90 = 1.0
@@ -107,13 +109,13 @@ def rsx(close: Series, length: int = None, drift: int = None, offset: int = None
if offset != 0:
rsx = rsx.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
rsx.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
rsx.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
rsx.name = f"RSX_{length}"
rsx.category = "momentum"
+9 -7
View File
@@ -4,8 +4,11 @@ from pandas_ta.overlap import swma
from pandas_ta.utils import get_offset, non_zero_range, verify_series
def rvgi(open_: Series, high: Series, low: Series, close: Series, length: int = None, swma_length: int = None,
offset: int = None, **kwargs) -> Series:
def rvgi(
open_: Series, high: Series, low: Series, close: Series,
length: int = None, swma_length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Relative Vigor Index (RVGI)
The Relative Vigor Index attempts to measure the strength of a trend relative to
@@ -32,7 +35,7 @@ def rvgi(open_: Series, high: Series, low: Series, close: Series, length: int =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
high_low_range = non_zero_range(high, low)
close_open_range = non_zero_range(close, open_)
length = int(length) if length and length > 0 else 14
@@ -46,7 +49,7 @@ def rvgi(open_: Series, high: Series, low: Series, close: Series, length: int =
if open_ is None or high is None or low is None or close is None: return
# Calculate Result
# Calculate
numerator = swma(close_open_range, length=swma_length).rolling(length).sum()
denominator = swma(high_low_range, length=swma_length).rolling(length).sum()
@@ -58,7 +61,7 @@ def rvgi(open_: Series, high: Series, low: Series, close: Series, length: int =
rvgi = rvgi.shift(offset)
signal = signal.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
rvgi.fillna(kwargs["fillna"], inplace=True)
signal.fillna(kwargs["fillna"], inplace=True)
@@ -66,12 +69,11 @@ def rvgi(open_: Series, high: Series, low: Series, close: Series, length: int =
rvgi.fillna(method=kwargs["fill_method"], inplace=True)
signal.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
rvgi.name = f"RVGI_{length}_{swma_length}"
signal.name = f"RVGIs_{length}_{swma_length}"
rvgi.category = signal.category = "momentum"
# Prepare DataFrame to return
df = DataFrame({rvgi.name: rvgi, signal.name: signal})
df.name = f"RVGI_{length}_{swma_length}"
df.category = rvgi.category
+14 -12
View File
@@ -1,18 +1,20 @@
# -*- coding: utf-8 -*-
from numpy import arctan as npAtan
from numpy import pi as npPi
from pandas_ta.utils import get_offset, verify_series
from numpy import arctan, pi
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def slope( close: Series, length: int = None, as_angle=None, to_degrees=None, vertical=None,
offset: int = None, **kwargs) -> Series:
def slope(
close: Series, length: int = None,
as_angle=None, to_degrees=None, vertical=None,
offset: int = None, **kwargs
) -> Series:
"""Slope
Returns the slope of a series of length n. Can convert the slope to angle.
Default: slope.
Sources: Algebra I
Source: Algebra
Calculation:
Default Inputs:
@@ -38,7 +40,7 @@ def slope( close: Series, length: int = None, as_angle=None, to_degrees=None, ve
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 1
as_angle = True if isinstance(as_angle, bool) else False
to_degrees = True if isinstance(to_degrees, bool) else False
@@ -47,24 +49,24 @@ def slope( close: Series, length: int = None, as_angle=None, to_degrees=None, ve
if close is None: return
# Calculate Result
# Calculate
slope = close.diff(length) / length
if as_angle:
slope = slope.apply(npAtan)
slope = slope.apply(arctan)
if to_degrees:
slope *= 180 / npPi
slope *= 180 / pi
# Offset
if offset != 0:
slope = slope.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
slope.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
slope.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
slope.name = f"SLOPE_{length}" if not as_angle else f"ANGLE{'d' if to_degrees else 'r'}_{length}"
slope.category = "momentum"
+10 -9
View File
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from .tsi import tsi
from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series
from .tsi import tsi
def smi(close: Series, fast: int = None, slow: int = None, signal: int = None, scalar: float = None,
offset: int = None, **kwargs) -> DataFrame:
def smi(
close: Series, fast: int = None, slow: int = None, signal: int = None,
scalar: float = None,
offset: int = None, **kwargs
) -> DataFrame:
"""SMI Ergodic Indicator (SMI)
The SMI Ergodic Indicator is the same as the True Strength Index (TSI) developed
@@ -37,7 +39,7 @@ def smi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
Returns:
pd.DataFrame: smi, signal, oscillator columns.
"""
# Validate arguments
# Validate
fast = int(fast) if fast and fast > 0 else 5
slow = int(slow) if slow and slow > 0 else 20
signal = int(signal) if signal and signal > 0 else 5
@@ -49,7 +51,7 @@ def smi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
if close is None: return
# Calculate Result
# Calculate
tsi_df = tsi(close, fast=fast, slow=slow, signal=signal, scalar=scalar)
smi = tsi_df.iloc[:, 0]
signalma = tsi_df.iloc[:, 1]
@@ -61,7 +63,7 @@ def smi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
signalma = signalma.shift(offset)
osc = osc.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
smi.fillna(kwargs["fillna"], inplace=True)
signalma.fillna(kwargs["fillna"], inplace=True)
@@ -71,7 +73,7 @@ def smi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
signalma.fillna(method=kwargs["fill_method"], inplace=True)
osc.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_scalar = f"_{scalar}" if scalar != 1 else ""
_props = f"_{fast}_{slow}_{signal}{_scalar}"
smi.name = f"SMI{_props}"
@@ -79,7 +81,6 @@ def smi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
osc.name = f"SMIo{_props}"
smi.category = signalma.category = osc.category = "momentum"
# Prepare DataFrame to return
data = {smi.name: smi, signalma.name: signalma, osc.name: osc}
df = DataFrame(data)
df.name = f"SMI{_props}"
+22 -18
View File
@@ -1,17 +1,21 @@
# -*- coding: utf-8 -*-
from numpy import nan as npNaN
from numpy import nan
from pandas import DataFrame, Series
from pandas_ta.momentum import mom
from pandas_ta.overlap import ema, linreg, sma
from pandas_ta.trend import decreasing, increasing
from pandas_ta.utils import get_offset, unsigned_differences, verify_series
from pandas_ta.volatility import bbands, kc
from pandas_ta.utils import get_offset
from pandas_ta.utils import unsigned_differences, verify_series
from .mom import mom
def squeeze(high: Series, low: Series, close: Series, bb_length: int = None, bb_std: float = None,
kc_length: int = None, kc_scalar: float = None, mom_length: int = None, mom_smooth: int = None,
use_tr=None, mamode: str = None, offset: int = None, **kwargs) -> DataFrame:
def squeeze(
high: Series, low: Series, close: Series,
bb_length: int = None, bb_std: float = None,
kc_length: int = None, kc_scalar: float = None,
mom_length: int = None, mom_smooth: int = None,
use_tr=None, mamode: str = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Squeeze (SQZ)
The default is based on John Carter's "TTM Squeeze" indicator, as discussed
@@ -54,7 +58,7 @@ def squeeze(high: Series, low: Series, close: Series, bb_length: int = None, bb_
pd.DataFrame: SQZ, SQZ_ON, SQZ_OFF, NO_SQZ columns by default. More
detailed columns if 'detailed' kwarg is True.
"""
# Validate arguments
# Validate
bb_length = int(bb_length) if bb_length and bb_length > 0 else 20
bb_std = float(bb_std) if bb_std and bb_std > 0 else 2.0
kc_length = int(kc_length) if kc_length and kc_length > 0 else 20
@@ -79,7 +83,7 @@ def squeeze(high: Series, low: Series, close: Series, bb_length: int = None, bb_
df.columns = df.columns.str.lower()
return [c.split("_")[0][n - 1:n] for c in df.columns]
# Calculate Result
# Calculate
bbd = bbands(close, length=bb_length, std=bb_std, mamode=mamode)
kch = kc(high, low, close, length=kc_length, scalar=kc_scalar, mamode=mamode, tr=use_tr)
@@ -113,7 +117,7 @@ def squeeze(high: Series, low: Series, close: Series, bb_length: int = None, bb_
squeeze_off = squeeze_off.shift(offset)
no_squeeze = no_squeeze.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
squeeze.fillna(kwargs["fillna"], inplace=True)
squeeze_on.fillna(kwargs["fillna"], inplace=True)
@@ -125,7 +129,7 @@ def squeeze(high: Series, low: Series, close: Series, bb_length: int = None, bb_
squeeze_off.fillna(method=kwargs["fill_method"], inplace=True)
no_squeeze.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = "" if use_tr else "hlr"
_props += f"_{bb_length}_{bb_std}_{kc_length}_{kc_scalar}"
_props += "_LB" if lazybear else ""
@@ -141,7 +145,7 @@ def squeeze(high: Series, low: Series, close: Series, bb_length: int = None, bb_
df.name = squeeze.name
df.category = squeeze.category = "momentum"
# Detailed Squeeze Series
# More Detail
if detailed:
pos_squeeze = squeeze[squeeze >= 0]
neg_squeeze = squeeze[squeeze < 0]
@@ -154,15 +158,15 @@ def squeeze(high: Series, low: Series, close: Series, bb_length: int = None, bb_
neg_dec *= squeeze
neg_inc *= squeeze
pos_inc.replace(0, npNaN, inplace=True)
pos_dec.replace(0, npNaN, inplace=True)
neg_dec.replace(0, npNaN, inplace=True)
neg_inc.replace(0, npNaN, inplace=True)
pos_inc.replace(0, nan, inplace=True)
pos_dec.replace(0, nan, inplace=True)
neg_dec.replace(0, nan, inplace=True)
neg_inc.replace(0, nan, inplace=True)
sqz_inc = squeeze * increasing(squeeze)
sqz_dec = squeeze * decreasing(squeeze)
sqz_inc.replace(0, npNaN, inplace=True)
sqz_dec.replace(0, npNaN, inplace=True)
sqz_inc.replace(0, nan, inplace=True)
sqz_dec.replace(0, nan, inplace=True)
# Handle fills
if "fillna" in kwargs:
+13 -18
View File
@@ -4,8 +4,11 @@ from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, non_zero_range, verify_series
def stc(close: Series, tclength: int = None, fast: int = None, slow: int = None, factor: float = None,
offset: int = None, **kwargs) -> DataFrame:
def stc(
close: Series, tclength: int = None,
fast: int = None, slow: int = None, factor: float = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Schaff Trend Cycle (STC)
The Schaff Trend Cycle is an evolution of the popular MACD incorportating two
@@ -49,7 +52,7 @@ def stc(close: Series, tclength: int = None, fast: int = None, slow: int = None,
Returns:
pd.DataFrame: stc, macd, stoch
"""
# Validate arguments
# Validate
tclength = int(tclength) if tclength and tclength > 0 else 10
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
@@ -62,6 +65,7 @@ def stc(close: Series, tclength: int = None, fast: int = None, slow: int = None,
if close is None: return
# Calculate
# kwargs allows for three more series (ma1, ma2 and osc) which can be passed
# here ma1 and ma2 input negate internal ema calculations, osc substitutes
# both ma's.
@@ -75,30 +79,22 @@ def stc(close: Series, tclength: int = None, fast: int = None, slow: int = None,
ma2 = verify_series(ma2, _length)
if ma1 is None or ma2 is None: return
# Calculate Result based on external feeded series
# According to external feeded series
xmacd = ma1 - ma2
# invoke shared calculation
pff, pf = schaff_tc(close, xmacd, tclength, factor)
elif isinstance(osc, Series):
osc = verify_series(osc, _length)
if osc is None: return
# Calculate Result based on feeded oscillator
# (should be ranging around 0 x-axis)
# According to feeded oscillator (should be ranging around 0 x-axis)
xmacd = osc
# invoke shared calculation
pff, pf = schaff_tc(close, xmacd, tclength, factor)
else:
# Calculate Result .. (traditionel/full)
# MACD line
# MACD (traditional/full)
fastma = ema(close, length=fast)
slowma = ema(close, length=slow)
xmacd = fastma - slowma
# invoke shared calculation
pff, pf = schaff_tc(close, xmacd, tclength, factor)
# Resulting Series
stc = Series(pff, index=close.index)
macd = Series(xmacd, index=close.index)
stoch = Series(pf, index=close.index)
@@ -109,7 +105,7 @@ def stc(close: Series, tclength: int = None, fast: int = None, slow: int = None,
macd = macd.shift(offset)
stoch = stoch.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
stc.fillna(kwargs["fillna"], inplace=True)
macd.fillna(kwargs["fillna"], inplace=True)
@@ -119,14 +115,13 @@ def stc(close: Series, tclength: int = None, fast: int = None, slow: int = None,
macd.fillna(method=kwargs["fill_method"], inplace=True)
stoch.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
_props = f"_{tclength}_{fast}_{slow}_{factor}"
stc.name = f"STC{_props}"
macd.name = f"STCmacd{_props}"
stoch.name = f"STCstoch{_props}"
stc.category = macd.category = stoch.category ="momentum"
# Prepare DataFrame to return
data = {stc.name: stc, macd.name: macd, stoch.name: stoch}
df = DataFrame(data)
df.name = f"STC{_props}"
@@ -170,4 +165,4 @@ def schaff_tc(close, xmacd, tclength, factor):
# Smoothed Calculation for % Fast D of PF
pff[i] = round(pff[i - 1] + (factor * (stoch2[i] - pff[i - 1])), 8)
return [pff, pf]
return pff, pf
+9 -5
View File
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from pandas_ta import Imports
from pandas_ta.overlap import ma
from pandas_ta.ma import ma
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, non_zero_range, tal_ma, verify_series
def stoch(high: Series, low: Series, close: Series, k: int = None, d: int = None, smooth_k: int = None,
mamode: str = None, talib: bool = None, offset: int = None, **kwargs) -> DataFrame:
def stoch(
high: Series, low: Series, close: Series,
k: int = None, d: int = None, smooth_k: int = None,
mamode: str = None, talib: bool = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Stochastic (STOCH)
The Stochastic Oscillator (STOCH) was developed by George Lane in the 1950's.
@@ -90,9 +94,9 @@ def stoch(high: Series, low: Series, close: Series, k: int = None, d: int = None
stoch_d.name = f"{_name}d{_props}"
stoch_k.category = stoch_d.category = "momentum"
# Return DataFrame
data = {stoch_k.name: stoch_k, stoch_d.name: stoch_d}
df = DataFrame(data, index=close.index)
df.name = f"{_name}{_props}"
df.category = stoch_k.category
return df
+8 -5
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from pandas_ta import Imports
from pandas_ta.overlap import ma
from pandas_ta.ma import ma
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, non_zero_range, tal_ma, verify_series
def stochf(high: Series, low: Series, close: Series, k: int = None, d: int = None, mamode: str = None,
talib: bool = None, offset: int = None, **kwargs) -> DataFrame:
def stochf(
high: Series, low: Series, close: Series, k: int = None, d: int = None,
mamode: str = None, talib: bool = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Fast Stochastic (STOCHF)
The Fast Stochastic Oscillator (STOCHF) was developed by George Lane in the
@@ -81,9 +84,9 @@ def stochf(high: Series, low: Series, close: Series, k: int = None, d: int = Non
stochf_d.name = f"{_name}d{_props}"
stochf_k.category = stochf_d.category = "momentum"
# Return DataFrame
data = {stochf_k.name: stochf_k, stochf_d.name: stochf_d}
df = DataFrame(data, index=close.index)
df.name = f"{_name}{_props}"
df.category = stochf_k.category
return df
+8 -6
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from .rsi import rsi
from pandas_ta.overlap import ma
from pandas_ta.ma import ma
from pandas_ta.momentum import rsi
from pandas_ta.utils import get_offset, non_zero_range, verify_series
def stochrsi(close: Series, length: int = None, rsi_length: int = None, k: int = None, d: int = None,
mamode: str = None, offset: int = None, **kwargs) -> DataFrame:
def stochrsi(
close: Series, length: int = None, rsi_length: int = None,
k: int = None, d: int = None, mamode: str = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Stochastic (STOCHRSI)
"Stochastic RSI and Dynamic Momentum Index" was created by Tushar Chande and Stanley Kroll and published in Stock & Commodities V.11:5 (189-199)
@@ -72,14 +75,13 @@ def stochrsi(close: Series, length: int = None, rsi_length: int = None, k: int =
stochrsi_k.fillna(method=kwargs["fill_method"], inplace=True)
stochrsi_d.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize
# Name and Category
_name = "STOCHRSI"
_props = f"_{length}_{rsi_length}_{k}_{d}"
stochrsi_k.name = f"{_name}k{_props}"
stochrsi_d.name = f"{_name}d{_props}"
stochrsi_k.category = stochrsi_d.category = "momentum"
# Return DataFrame
data = {stochrsi_k.name: stochrsi_k, stochrsi_d.name: stochrsi_d}
df = DataFrame(data)
df.name = f"{_name}{_props}"
+38 -32
View File
@@ -1,22 +1,25 @@
# -*- coding: utf-8 -*-
# import numpy as np
from numpy import where as npWhere
from numpy import where
from pandas import DataFrame, Series
from pandas_ta.utils import get_offset, verify_series
def td_seq(close: Series, asint: bool = None, offset: int = None, **kwargs) -> DataFrame:
def td_seq(
close: Series, asint: bool = None,
offset: int = None, **kwargs
) -> DataFrame:
"""TD Sequential (TD_SEQ)
Tom DeMark's Sequential indicator attempts to identify a price point where an
uptrend or a downtrend exhausts itself and reverses.
Tom DeMark's Sequential indicator attempts to identify a price point
where an uptrend or a downtrend exhausts itself and reverses.
Sources:
https://tradetrekker.wordpress.com/tdsequential/
Args:
close (pd.Series): Series of 'close's
asint (bool): If True, fillnas with 0 and change type to int. Default: False
asint (bool): If True, fillnas with 0 and change type to int.
Default: False
offset (int): How many periods to offset the result. Default: 0
Kwargs:
@@ -26,35 +29,15 @@ def td_seq(close: Series, asint: bool = None, offset: int = None, **kwargs) -> D
Returns:
pd.DataFrame: New feature generated.
"""
# Validate arguments
# Validate
close = verify_series(close)
offset = get_offset(offset)
asint = asint if isinstance(asint, bool) else False
show_all = kwargs.setdefault("show_all", True)
def true_sequence_count(series: Series):
index = series.where(series == False).last_valid_index()
if index is None:
return series.count()
else:
s = series[series.index > index]
return s.count()
def calc_td(series: Series, direction: str, show_all: bool):
td_bool = series.diff(4) > 0 if direction=="up" else series.diff(4) < 0
td_num = npWhere(
td_bool, td_bool.rolling(13, min_periods=0).apply(true_sequence_count), 0
)
td_num = Series(td_num)
if show_all:
td_num = td_num.mask(td_num == 0)
else:
td_num = td_num.mask(~td_num.between(6,9))
return td_num
if close is None: return
# Calculate
up_seq = calc_td(close, "up", show_all)
down_seq = calc_td(close, "down", show_all)
@@ -70,7 +53,7 @@ def td_seq(close: Series, asint: bool = None, offset: int = None, **kwargs) -> D
up_seq = up_seq.shift(offset)
down_seq = down_seq.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
up_seq.fillna(kwargs["fillna"], inplace=True)
down_seq.fillna(kwargs["fillna"], inplace=True)
@@ -79,12 +62,11 @@ def td_seq(close: Series, asint: bool = None, offset: int = None, **kwargs) -> D
up_seq.fillna(method=kwargs["fill_method"], inplace=True)
down_seq.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
up_seq.name = f"TD_SEQ_UPa" if show_all else f"TD_SEQ_UP"
down_seq.name = f"TD_SEQ_DNa" if show_all else f"TD_SEQ_DN"
up_seq.category = down_seq.category = "momentum"
# Prepare Dataframe to return
data = {up_seq.name: up_seq, down_seq.name: down_seq}
df = DataFrame(data)
df.index = close.index # Only works here for some reason?
@@ -92,3 +74,27 @@ def td_seq(close: Series, asint: bool = None, offset: int = None, **kwargs) -> D
df.category = up_seq.category
return df
def sequence_count(series: Series):
index = series.where(series == False).last_valid_index()
if index is None:
return series.count()
else:
s = series[series.index > index]
return s.count()
def calc_td(series: Series, direction: str, show_all: bool):
td_bool = series.diff(4) > 0 if direction=="up" else series.diff(4) < 0
td_num = where(
td_bool, td_bool.rolling(13, min_periods=0).apply(sequence_count), 0
)
td_num = Series(td_num)
if show_all:
td_num = td_num.mask(td_num == 0)
else:
td_num = td_num.mask(~td_num.between(6,9))
return td_num
+20 -11
View File
@@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
# from numpy import isnan
from pandas import DataFrame, Series
from pandas_ta.overlap.ema import ema
from pandas_ta.utils import get_drift, get_offset, verify_series
def trix(close: Series, length: int = None, signal: int = None, scalar: float = None, drift: int = None,
offset: int = None, **kwargs) -> Series:
def trix(
close: Series, length: int = None, signal: int = None,
scalar: float = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Trix (TRIX)
TRIX is a momentum oscillator to identify divergences.
@@ -28,22 +32,28 @@ def trix(close: Series, length: int = None, signal: int = None, scalar: float =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
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
close = verify_series(close, max(length, signal))
_length = 3 * length - 2
close = verify_series(close, _length)
drift = get_drift(drift)
offset = get_offset(offset)
if close is None: return
# Calculate Result
# Calculate
ema1 = ema(close=close, length=length, **kwargs)
ema2 = ema(close=ema1, length=length, **kwargs)
ema3 = ema(close=ema2, length=length, **kwargs)
trix = scalar * ema3.pct_change(drift)
# if all(isnan(ema1)): return # Emergency Break
ema2 = ema(close=ema1, length=length, **kwargs)
# if all(isnan(ema2)): return # Emergency Break
ema3 = ema(close=ema2, length=length, **kwargs)
# if all(isnan(ema3)): return # Emergency Break
trix = scalar * ema3.pct_change(drift)
trix_signal = trix.rolling(signal).mean()
# Offset
@@ -51,7 +61,7 @@ def trix(close: Series, length: int = None, signal: int = None, scalar: float =
trix = trix.shift(offset)
trix_signal = trix_signal.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
trix.fillna(kwargs["fillna"], inplace=True)
trix_signal.fillna(kwargs["fillna"], inplace=True)
@@ -59,12 +69,11 @@ def trix(close: Series, length: int = None, signal: int = None, scalar: float =
trix.fillna(method=kwargs["fill_method"], inplace=True)
trix_signal.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
trix.name = f"TRIX_{length}_{signal}"
trix_signal.name = f"TRIXs_{length}_{signal}"
trix.category = trix_signal.category = "momentum"
# Prepare DataFrame to return
data = {trix.name: trix, trix_signal.name: trix_signal}
df = DataFrame(data, index=close.index)
df.name = f"TRIX_{length}_{signal}"
+11 -8
View File
@@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from pandas_ta.overlap import ema, ma
from pandas_ta.ma import ma
from pandas_ta.overlap import ema
from pandas_ta.utils import get_drift, get_offset, verify_series
def tsi(close: Series, fast: int = None, slow: int = None, signal: int = None, scalar: float = None,
mamode: str = None, drift: int = None, offset: int = None, **kwargs) -> DataFrame:
def tsi(
close: Series, fast: int = None, slow: int = None, signal: int = None,
scalar: float = None, mamode: str = None, drift: int = None,
offset: int = None, **kwargs
) -> DataFrame:
"""True Strength Index (TSI)
The True Strength Index is a momentum indicator used to identify short-term
@@ -33,7 +37,7 @@ def tsi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
Returns:
pd.DataFrame: tsi, signal.
"""
# Validate Arguments
# Validate
fast = int(fast) if fast and fast > 0 else 13
slow = int(slow) if slow and slow > 0 else 25
signal = int(signal) if signal and signal > 0 else 13
@@ -48,7 +52,7 @@ def tsi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
if close is None: return
# Calculate Result
# Calculate
diff = close.diff(drift)
slow_ema = ema(close=diff, length=slow, **kwargs)
fast_slow_ema = ema(close=slow_ema, length=fast, **kwargs)
@@ -65,7 +69,7 @@ def tsi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
tsi = tsi.shift(offset)
tsi_signal = tsi_signal.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
tsi.fillna(kwargs["fillna"], inplace=True)
tsi_signal.fillna(kwargs["fillna"], inplace=True)
@@ -73,12 +77,11 @@ def tsi(close: Series, fast: int = None, slow: int = None, signal: int = None, s
tsi.fillna(method=kwargs["fill_method"], inplace=True)
tsi_signal.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
tsi.name = f"TSI_{fast}_{slow}_{signal}"
tsi_signal.name = f"TSIs_{fast}_{slow}_{signal}"
tsi.category = tsi_signal.category = "momentum"
# Prepare DataFrame to return
df = DataFrame({tsi.name: tsi, tsi_signal.name: tsi_signal})
df.name = f"TSI_{fast}_{slow}_{signal}"
df.category = "momentum"
+12 -8
View File
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series
from pandas_ta import Imports
from pandas_ta.maps import Imports
from pandas_ta.utils import get_drift, get_offset, verify_series
def uo(high: Series, low: Series, close: Series, fast: int = None, medium: int = None, slow: int = None,
fast_w: float = None, medium_w: float = None, slow_w: float = None, talib: bool = None, drift: int = None,
offset: int = None, **kwargs) -> Series:
def uo(
high: Series, low: Series, close: Series,
fast: int = None, medium: int = None, slow: int = None,
fast_w: float = None, medium_w: float = None, slow_w: float = None,
talib: bool = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Ultimate Oscillator (UO)
The Ultimate Oscillator is a momentum indicator over three different
@@ -37,7 +41,7 @@ def uo(high: Series, low: Series, close: Series, fast: int = None, medium: int =
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
fast = int(fast) if fast and fast > 0 else 7
fast_w = float(fast_w) if fast_w and fast_w > 0 else 4.0
medium = int(medium) if medium and medium > 0 else 14
@@ -54,7 +58,7 @@ def uo(high: Series, low: Series, close: Series, fast: int = None, medium: int =
if high is None or low is None or close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import ULTOSC
uo = ULTOSC(high, low, close, fast, medium, slow)
@@ -83,13 +87,13 @@ def uo(high: Series, low: Series, close: Series, fast: int = None, medium: int =
if offset != 0:
uo = uo.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
uo.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
uo.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
uo.name = f"UO_{fast}_{medium}_{slow}"
uo.category = "momentum"
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
def willr(high: Series, low: Series, close: Series, length: int = None, talib: bool = None, offset: int = None,
**kwargs) -> Series:
def willr(
high: Series, low: Series, close: Series,
length: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""William's Percent R (WILLR)
William's Percent R is a momentum oscillator similar to the RSI that
@@ -30,7 +33,7 @@ def willr(high: Series, low: Series, close: Series, length: int = None, talib: b
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 14
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
_length = max(length, min_periods)
@@ -42,7 +45,7 @@ def willr(high: Series, low: Series, close: Series, length: int = None, talib: b
if high is None or low is None or close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import WILLR
willr = WILLR(high, low, close, length)
@@ -56,13 +59,13 @@ def willr(high: Series, low: Series, close: Series, length: int = None, talib: b
if offset != 0:
willr = willr.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
willr.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
willr.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
willr.name = f"WILLR_{length}"
willr.category = "momentum"
-1
View File
@@ -13,7 +13,6 @@ from .ichimoku import ichimoku
from .jma import jma
from .kama import kama
from .linreg import linreg
from .ma import ma
from .mcgd import mcgd
from .midpoint import midpoint
from .midprice import midprice
+10 -8
View File
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
# from numpy import nan as npNaN
from pandas import DataFrame, Series
from .smma import smma
from pandas_ta.utils import get_offset, verify_series
from .smma import smma
def alligator(close: Series, jaw: int = None, teeth: int = None, lips: int = None, talib: bool = None,
offset: int = None, **kwargs) -> DataFrame:
def alligator(
close: Series, jaw: int = None, teeth: int = None, lips: int = None,
talib: bool = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Bill Williams Alligator (ALLIGATOR)
The Alligator Indicator was developed by Bill Williams and combines moving
@@ -37,7 +39,7 @@ def alligator(close: Series, jaw: int = None, teeth: int = None, lips: int = Non
Returns:
pd.DataFrame: JAW, TEETH, LIPS columns.
"""
# Validate Arguments
# Validate
jaw = int(jaw) if jaw and jaw > 0 else 13
teeth = int(teeth) if teeth and teeth > 0 else 8
lips = int(lips) if lips and lips > 0 else 5
@@ -47,7 +49,7 @@ def alligator(close: Series, jaw: int = None, teeth: int = None, lips: int = Non
if close is None: return
# Calculate Result
# Calculate
gator_jaw = smma(close, length=jaw, talib=mode_tal)
gator_teeth = smma(close, length=teeth, talib=mode_tal)
gator_lips = smma(close, length=lips, talib=mode_tal)
@@ -58,7 +60,7 @@ def alligator(close: Series, jaw: int = None, teeth: int = None, lips: int = Non
gator_teeth = gator_teeth.shift(offset)
gator_lips = gator_lips.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
gator_jaw.fillna(kwargs["fillna"], inplace=True)
gator_teeth.fillna(kwargs["fillna"], inplace=True)
@@ -68,7 +70,7 @@ def alligator(close: Series, jaw: int = None, teeth: int = None, lips: int = Non
gator_teeth.fillna(method=kwargs["fill_method"], inplace=True)
gator_lips.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
_props = f"_{jaw}_{teeth}_{lips}"
data = {
f"AGj{_props}": gator_jaw,
+17 -19
View File
@@ -1,18 +1,15 @@
# -*- coding: utf-8 -*-
from numpy import floor as npFloor
from numpy import append as npAppend
from numpy import arange as npArange
from numpy import array as npArray
from numpy import exp as npExp
from numpy import nan as npNaN
from numpy import tensordot as npTensordot
from numpy import append, arange, array, exp, floor, nan, tensordot
from numpy.version import version as npVersion
from pandas import Series
from pandas_ta.utils import get_offset, strided_window, verify_series
def alma(close: Series, length: int = None, sigma: float = None, dist_offset: float = None, offset: int = None,
**kwargs) -> Series:
def alma(
close: Series, length: int = None,
sigma: float = None, dist_offset: float = None,
offset: int = None, **kwargs
) -> Series:
"""Arnaud Legoux Moving Average (ALMA)
The ALMA moving average uses the curve of the Normal (Gauss) distribution, which
@@ -40,7 +37,7 @@ def alma(close: Series, length: int = None, sigma: float = None, dist_offset: fl
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if isinstance(length, int) and length > 0 else 9
sigma = float(sigma) if isinstance(sigma, float) and sigma > 0 else 6.0
if isinstance(dist_offset, float) and dist_offset >= 0 and dist_offset <= 1:
@@ -52,31 +49,32 @@ def alma(close: Series, length: int = None, sigma: float = None, dist_offset: fl
if close is None: return
# Calculate Result
x = npArange(length)
k = npFloor(offset_ * (length - 1))
weights = npExp(-0.5 * ((sigma / length) * (x - k)) ** 2)
# Calculate
np_close = close.values
x = arange(length)
k = floor(offset_ * (length - 1))
weights = exp(-0.5 * ((sigma / length) * (x - k)) ** 2)
weights /= weights.sum()
if npVersion >= "1.20.0":
from numpy.lib.stride_tricks import sliding_window_view
window = sliding_window_view(npArray(close), length)
window = sliding_window_view(np_close, length)
else:
window = strided_window(npArray(close), length)
result = npAppend(npArray([npNaN] * (length - 1)), npTensordot(window, weights, axes=1))
window = strided_window(np_close, length)
result = append(array([nan] * (length - 1)), tensordot(window, weights, axes=1))
alma = Series(result, index=close.index)
# Offset
if offset != 0:
alma = alma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
alma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
alma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
alma.name = f"ALMA_{length}_{sigma}_{offset_}"
alma.category = "overlap"
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from .ema import ema
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
from .ema import ema
def dema(close: Series, length: int = None, talib: bool = None, offset: int = None, **kwargs) -> Series:
def dema(
close: Series, length: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Double Exponential Moving Average (DEMA)
The Double Exponential Moving Average attempts to a smoother average with less
@@ -28,7 +31,7 @@ def dema(close: Series, length: int = None, talib: bool = None, offset: int = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
offset = get_offset(offset)
@@ -36,7 +39,7 @@ def dema(close: Series, length: int = None, talib: bool = None, offset: int = No
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import DEMA
dema = DEMA(close, length)
@@ -49,13 +52,13 @@ def dema(close: Series, length: int = None, talib: bool = None, offset: int = No
if offset != 0:
dema = dema.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
dema.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
dema.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
dema.name = f"DEMA_{length}"
dema.category = "overlap"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import fibonacci, get_offset, verify_series, weights
from pandas import Series
from pandas_ta.utils import fibonacci, get_offset, verify_series, weights
def fwma(close: Series, length: int = None, asc: bool = None, offset: int = None, **kwargs) -> Series:
def fwma(
close: Series, length: int = None, asc: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Fibonacci's Weighted Moving Average (FWMA)
Fibonacci's Weighted Moving Average is similar to a Weighted Moving Average
@@ -24,7 +27,7 @@ def fwma(close: Series, length: int = None, asc: bool = None, offset: int = None
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
asc = asc if asc else True
close = verify_series(close, length)
@@ -32,7 +35,7 @@ def fwma(close: Series, length: int = None, asc: bool = None, offset: int = None
if close is None: return
# Calculate Result
# Calculate
fibs = fibonacci(n=length, weighted=True)
fwma = close.rolling(length, min_periods=length).apply(weights(fibs), raw=True)
@@ -40,13 +43,13 @@ def fwma(close: Series, length: int = None, asc: bool = None, offset: int = None
if offset != 0:
fwma = fwma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
fwma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
fwma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
fwma.name = f"FWMA_{length}"
fwma.category = "overlap"
+14 -11
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from numpy import nan as npNaN
from numpy import nan
from pandas import DataFrame, Series
from .ma import ma
from pandas_ta.ma import ma
from pandas_ta.utils import get_offset, verify_series
def hilo(high: Series, low: Series, close: Series, high_length: int = None, low_length: int = None,
mamode: str = None, offset: int = None, **kwargs) -> DataFrame:
def hilo(
high: Series, low: Series, close: Series,
high_length: int = None, low_length: int = None, mamode: str = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Gann HiLo Activator(HiLo)
The Gann High Low Activator Indicator was created by Robert Krausz in a 1998
@@ -42,7 +45,7 @@ def hilo(high: Series, low: Series, close: Series, high_length: int = None, low_
Returns:
pd.DataFrame: HILO (line), HILOl (long), HILOs (short) columns.
"""
# Validate Arguments
# Validate
high_length = int(high_length) if high_length and high_length > 0 else 13
low_length = int(low_length) if low_length and low_length > 0 else 21
mamode = mamode.lower() if isinstance(mamode, str) else "sma"
@@ -54,11 +57,11 @@ def hilo(high: Series, low: Series, close: Series, high_length: int = None, low_
if high is None or low is None or close is None: return
# Calculate Result
# Calculate
m = close.size
hilo = Series(npNaN, index=close.index)
long = Series(npNaN, index=close.index)
short = Series(npNaN, index=close.index)
hilo = Series(nan, index=close.index)
long = Series(nan, index=close.index)
short = Series(nan, index=close.index)
high_ma = ma(mamode, high, length=high_length)
low_ma = ma(mamode, low, length=low_length)
@@ -78,7 +81,7 @@ def hilo(high: Series, low: Series, close: Series, high_length: int = None, low_
long = long.shift(offset)
short = short.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
hilo.fillna(kwargs["fillna"], inplace=True)
long.fillna(kwargs["fillna"], inplace=True)
@@ -88,7 +91,7 @@ def hilo(high: Series, low: Series, close: Series, high_length: int = None, low_
long.fillna(method=kwargs["fill_method"], inplace=True)
short.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
_props = f"_{high_length}_{low_length}"
data = {f"HILO{_props}": hilo, f"HILOl{_props}": long, f"HILOs{_props}": short}
df = DataFrame(data, index=close.index)
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def hl2(high: Series, low: Series, offset: int = None, **kwargs) -> Series:
def hl2(
high: Series, low: Series,
offset: int = None, **kwargs
) -> Series:
"""HL2
HL2 is the midpoint/average of high and low.
@@ -16,19 +19,19 @@ def hl2(high: Series, low: Series, offset: int = None, **kwargs) -> Series:
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
high = verify_series(high)
low = verify_series(low)
offset = get_offset(offset)
# Calculate Result
hl2 = 0.5 * (high + low)
# Calculate
hl2 = Series(0.5 * (high.values + low.values), index=high.index)
# Offset
if offset != 0:
hl2 = hl2.shift(offset)
# Name & Category
# Name and Category
hl2.name = "HL2"
hl2.category = "overlap"
+10 -7
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
def hlc3(high: Series, low: Series, close: Series, talib: bool = None, offset: int = None, **kwargs) -> Series:
def hlc3(
high: Series, low: Series, close: Series, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""HLC3
HLC3 is the average of high, low and close.
@@ -18,25 +21,25 @@ def hlc3(high: Series, low: Series, close: Series, talib: bool = None, offset: i
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
offset = get_offset(offset)
mode_tal = bool(talib) if isinstance(talib, bool) else True
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import TYPPRICE
hlc3 = TYPPRICE(high, low, close)
else:
hlc3 = (high + low + close) / 3.0
hlc3 = Series((high.values + low.values + close.values) / 3.0, index=close.index)
# Offset
if offset != 0:
hlc3 = hlc3.shift(offset)
# Name & Category
# Name and Category
hlc3.name = "HLC3"
hlc3.category = "overlap"
+12 -9
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from numpy import sqrt as npSqrt
from .wma import wma
from pandas_ta.utils import get_offset, verify_series
from numpy import sqrt
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
from .wma import wma
def hma(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def hma(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Hull Moving Average (HMA)
The Hull Exponential Moving Average attempts to reduce or remove lag in moving
@@ -26,16 +29,16 @@ def hma(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
offset = get_offset(offset)
if close is None: return
# Calculate Result
# Calculate
half_length = int(length / 2)
sqrt_length = int(npSqrt(length))
sqrt_length = int(sqrt(length))
wmaf = wma(close=close, length=half_length)
wmas = wma(close=close, length=length)
@@ -45,13 +48,13 @@ def hma(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
if offset != 0:
hma = hma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
hma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
hma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
hma.name = f"HMA_{length}"
hma.category = "overlap"
+8 -5
View File
@@ -3,7 +3,10 @@ from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def hwma(close: Series, na: float = None, nb: float = None, nc: float = None, offset: int = None, **kwargs) -> Series:
def hwma(
close: Series, na: float = None, nb: float = None, nc: float = None,
offset: int = None, **kwargs
) -> Series:
"""HWMA (Holt-Winter Moving Average)
Indicator HWMA (Holt-Winter Moving Average) is a three-parameter moving average
@@ -30,14 +33,14 @@ def hwma(close: Series, na: float = None, nb: float = None, nc: float = None, of
Returns:
pd.Series: hwma
"""
# Validate Arguments
# Validate
na = float(na) if na and na > 0 and na < 1 else 0.2
nb = float(nb) if nb and nb > 0 and nb < 1 else 0.1
nc = float(nc) if nc and nc > 0 and nc < 1 else 0.1
close = verify_series(close)
offset = get_offset(offset)
# Calculate Result
# Calculate
last_a = last_v = 0
last_f = close.iloc[0]
@@ -56,13 +59,13 @@ def hwma(close: Series, na: float = None, nb: float = None, nc: float = None, of
if offset != 0:
hwma = hwma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
hwma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
hwma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
suffix = f"{na}_{nb}_{nc}"
hwma.name = f"HWMA_{suffix}"
hwma.category = "overlap"
+11 -6
View File
@@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
from pandas import date_range, DataFrame, RangeIndex, Timedelta, Series
from .midprice import midprice
from pandas_ta.utils import get_offset, verify_series
from .midprice import midprice
def ichimoku(high: Series, low: Series, close: Series, tenkan: int = None, kijun: int = None, senkou: int = None,
include_chikou: bool = True, offset: int = None, **kwargs) -> DataFrame:
def ichimoku(
high: Series, low: Series, close: Series,
tenkan: int = None, kijun: int = None, senkou: int = None,
include_chikou: bool = True,
offset: int = None, **kwargs
) -> DataFrame:
"""Ichimoku Kinkō Hyō (ichimoku)
Developed Pre WWII as a forecasting model for financial markets.
@@ -33,6 +37,7 @@ def ichimoku(high: Series, low: Series, close: Series, tenkan: int = None, kijun
and chikou_span columns
For the forward looking period: spanA and spanB columns
"""
# Validate
tenkan = int(tenkan) if tenkan and tenkan > 0 else 9
kijun = int(kijun) if kijun and kijun > 0 else 26
senkou = int(senkou) if senkou and senkou > 0 else 52
@@ -46,7 +51,7 @@ def ichimoku(high: Series, low: Series, close: Series, tenkan: int = None, kijun
if high is None or low is None or close is None: return None, None
# Calculate Result
# Calculate
tenkan_sen = midprice(high=high, low=low, length=tenkan)
kijun_sen = midprice(high=high, low=low, length=kijun)
span_a = 0.5 * (tenkan_sen + kijun_sen)
@@ -68,7 +73,7 @@ def ichimoku(high: Series, low: Series, close: Series, tenkan: int = None, kijun
span_b = span_b.shift(offset)
chikou_span = chikou_span.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
span_a.fillna(kwargs["fillna"], inplace=True)
span_b.fillna(kwargs["fillna"], inplace=True)
@@ -78,7 +83,7 @@ def ichimoku(high: Series, low: Series, close: Series, tenkan: int = None, kijun
span_b.fillna(method=kwargs["fill_method"], inplace=True)
chikou_span.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
span_a.name = f"ISA_{tenkan}"
span_b.name = f"ISB_{kijun}"
tenkan_sen.name = f"ITS_{tenkan}"
+26 -26
View File
@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
from numpy import average as npAverage
from numpy import nan as npNaN
from numpy import log as npLog
from numpy import power as npPower
from numpy import sqrt as npSqrt
from numpy import zeros_like as npZeroslike
# from numpy import average, log, nan, power, sqrt, zeros_like
from numpy import average, log, nan, sqrt, zeros_like
from numpy import power as np_power
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def jma(close: Series, length: int = None, phase: float = None, offset: int = None, **kwargs) -> Series:
def jma(
close: Series, length: int = None, phase: float = None,
offset: int = None, **kwargs
) -> Series:
"""Jurik Moving Average Average (JMA)
Mark Jurik's Moving Average (JMA) attempts to eliminate noise to see the "true"
@@ -33,17 +33,17 @@ def jma(close: Series, length: int = None, phase: float = None, offset: int = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
_length = int(length) if length and length > 0 else 7
phase = float(phase) if phase and phase != 0 else 0
close = verify_series(close, _length)
offset = get_offset(offset)
if close is None: return
# Define base variables
jma = npZeroslike(close)
volty = npZeroslike(close)
v_sum = npZeroslike(close)
# Calculate
jma = zeros_like(close)
volty = zeros_like(close)
v_sum = zeros_like(close)
kv = det0 = det1 = ma2 = 0.0
jma[0] = ma1 = uBand = lBand = close[0]
@@ -52,9 +52,9 @@ def jma(close: Series, length: int = None, phase: float = None, offset: int = No
sum_length = 10
length = 0.5 * (_length - 1)
pr = 0.5 if phase < -100 else 2.5 if phase > 100 else 1.5 + phase * 0.01
length1 = max((npLog(npSqrt(length)) / npLog(2.0)) + 2.0, 0)
length1 = max((log(sqrt(length)) / log(2.0)) + 2.0, 0)
pow1 = max(length1 - 2.0, 0.5)
length2 = length1 * npSqrt(length)
length2 = length1 * sqrt(length)
bet = length2 / (length2 + 1)
beta = 0.45 * (_length - 1) / (0.45 * (_length - 1) + 2.0)
@@ -69,46 +69,46 @@ def jma(close: Series, length: int = None, phase: float = None, offset: int = No
# Relative price volatility factor
v_sum[i] = v_sum[i - 1] + (volty[i] - volty[max(i - sum_length, 0)]) / sum_length
avg_volty = npAverage(v_sum[max(i - 65, 0):i + 1])
avg_volty = average(v_sum[max(i - 65, 0):i + 1])
d_volty = 0 if avg_volty ==0 else volty[i] / avg_volty
r_volty = max(1.0, min(npPower(length1, 1 / pow1), d_volty))
r_volty = max(1.0, min(np_power(length1, 1 / pow1), d_volty))
# r_volty = max(1.0, min(length1 **(1 / pow1), d_volty))
# Jurik volatility bands
pow2 = npPower(r_volty, pow1)
kv = npPower(bet, npSqrt(pow2))
pow2 = np_power(r_volty, pow1)
kv = np_power(bet, sqrt(pow2))
uBand = price if (del1 > 0) else price - (kv * del1)
lBand = price if (del2 < 0) else price - (kv * del2)
# Jurik Dynamic Factor
power = npPower(r_volty, pow1)
alpha = npPower(beta, power)
power = np_power(r_volty, pow1)
alpha = np_power(beta, power)
# 1st stage - prelimimary smoothing by adaptive EMA
ma1 = ((1 - alpha) * price) + (alpha * ma1)
ma1 = (1 - alpha) * price + alpha * ma1
# 2nd stage - one more prelimimary smoothing by Kalman filter
det0 = ((price - ma1) * (1 - beta)) + (beta * det0)
det0 = (1 - beta) * (price - ma1) + beta * det0
ma2 = ma1 + pr * det0
# 3rd stage - final smoothing by unique Jurik adaptive filter
det1 = ((ma2 - jma[i - 1]) * (1 - alpha) * (1 - alpha)) + (alpha * alpha * det1)
jma[i] = jma[i-1] + det1
# Remove initial lookback data and convert to pandas frame
jma = Series(jma, index=close.index)
jma.iloc[0:_length - 1] = npNaN
jma.iloc[0:_length - 1] = nan
# Offset
if offset != 0:
jma = jma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
jma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
jma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
jma.name = f"JMA_{_length}_{phase}"
jma.category = "overlap"
+12 -9
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from numpy import nan as npNaN
from numpy import nan
from pandas import Series
from pandas_ta.overlap.ma import ma
from pandas_ta.ma import ma
from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series
def kama(close: Series, length: int = None, fast: int = None, slow: int = None, mamode: str = None,
drift: int = None, offset: int = None, **kwargs) -> Series:
def kama(
close: Series, length: int = None, fast: int = None, slow: int = None,
mamode: str = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Kaufman's Adaptive Moving Average (KAMA)
Developed by Perry Kaufman, Kaufman's Adaptive Moving Average (KAMA) is a moving average
@@ -37,7 +40,7 @@ def kama(close: Series, length: int = None, fast: int = None, slow: int = None,
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
fast = int(fast) if fast and fast > 0 else 2
slow = int(slow) if slow and slow > 0 else 30
@@ -55,7 +58,7 @@ def kama(close: Series, length: int = None, fast: int = None, slow: int = None,
if close is None: return
# Calculate Result
# Calculate
def weight(length: int) -> float:
return 2 / (length + 1)
@@ -71,7 +74,7 @@ def kama(close: Series, length: int = None, fast: int = None, slow: int = None,
m = close.size
ma0 = ma(mamode, close.iloc[:length], length=length, **kwargs).iloc[-1]
result = [npNaN for _ in range(0, length - 1)] + [ma0]
result = [nan for _ in range(0, length - 1)] + [ma0]
for i in range(length, m):
result.append(sc.iloc[i] * close.iloc[i] + (1 - sc.iloc[i]) * result[i - 1])
@@ -81,13 +84,13 @@ def kama(close: Series, length: int = None, fast: int = None, slow: int = None,
if offset != 0:
kama = kama.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
kama.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
kama.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
kama.name = f"KAMA_{length}_{fast}_{slow}"
kama.category = "overlap"
+23 -19
View File
@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
from numpy import array as npArray
from numpy import arctan as npAtan
from numpy import nan as npNaN
from numpy import pi as npPi
from numpy.version import version as npVersion
from numpy import arctan, nan, pi, zeros_like
from numpy.version import version
from pandas import Series
from pandas_ta import Imports
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, strided_window, verify_series
def linreg(close: Series, length: int = None, talib: int = None, offset: int = None, **kwargs) -> Series:
def linreg(
close: Series, length: int = None, talib: int = None,
offset: int = None, **kwargs
) -> Series:
"""Linear Regression Moving Average (linreg)
Linear Regression Moving Average (LINREG). This is a simplified version of a
@@ -42,7 +42,7 @@ def linreg(close: Series, length: int = None, talib: int = None, offset: int = N
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 14
close = verify_series(close, length)
offset = get_offset(offset)
@@ -56,7 +56,9 @@ def linreg(close: Series, length: int = None, talib: int = None, offset: int = N
if close is None: return
# Calculate Result
# Calculate
np_close = close.values
if Imports["talib"] and mode_tal:
from talib import LINEARREG, LINEARREG_ANGLE, LINEARREG_INTERCEPT, LINEARREG_SLOPE, TSF
if tsf:
@@ -70,6 +72,7 @@ def linreg(close: Series, length: int = None, talib: int = None, offset: int = N
else:
linreg = LINEARREG(close, timeperiod=length)
else:
linreg_ = zeros_like(np_close)
x = range(1, length + 1) # [1, 2, ..., n] from 1 to n keeps Sum(xy) low
x_sum = 0.5 * length * (length + 1)
x2_sum = x_sum * (2 * length + 1) / 3
@@ -87,9 +90,9 @@ def linreg(close: Series, length: int = None, talib: int = None, offset: int = N
return b
if angle:
theta = npAtan(m)
theta = arctan(m)
if degrees:
theta *= 180 / npPi
theta *= 180 / pi
return theta
if r:
@@ -100,25 +103,26 @@ def linreg(close: Series, length: int = None, talib: int = None, offset: int = N
return m * length + b if not tsf else m * (length - 1) + b
if npVersion >= "1.20.0":
if version >= "1.20.0":
from numpy.lib.stride_tricks import sliding_window_view
linreg_ = [linear_regression(_) for _ in sliding_window_view(npArray(close), length)]
else:
linreg_ = [linear_regression(_) for _ in strided_window(npArray(close), length)]
linreg_ = [linear_regression(_) for _ in sliding_window_view(np_close, length)]
linreg = Series([npNaN] * (length - 1) + linreg_, index=close.index)
else:
linreg_ = [linear_regression(_) for _ in strided_window(np_close, length)]
linreg = Series([nan] * (length - 1) + linreg_, index=close.index)
# Offset
if offset != 0:
linreg = linreg.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
linreg.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
linreg.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
linreg.name = f"LR"
if slope: linreg.name += "m"
if intercept: linreg.name += "b"
@@ -128,4 +132,4 @@ def linreg(close: Series, length: int = None, talib: int = None, offset: int = N
linreg.name += f"_{length}"
linreg.category = "overlap"
return linreg
return linreg
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def mcgd(close: Series, length: int = None, offset: int = None, c: float = None, **kwargs) -> Series:
def mcgd(
close: Series, length: int = None, c: float = None,
offset: int = None, **kwargs
) -> Series:
"""McGinley Dynamic Indicator
The McGinley Dynamic looks like a moving average line, yet it is actually a
@@ -30,7 +33,7 @@ def mcgd(close: Series, length: int = None, offset: int = None, c: float = None,
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 10
c = float(c) if c and 0 < c <= 1 else 1
close = verify_series(close, length)
@@ -38,7 +41,7 @@ def mcgd(close: Series, length: int = None, offset: int = None, c: float = None,
if close is None: return
# Calculate Result
# Calculate
close = close.copy()
def mcg_(series):
@@ -53,13 +56,13 @@ def mcgd(close: Series, length: int = None, offset: int = None, c: float = None,
if offset != 0:
mcg_ds = mcg_ds.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
mcg_ds.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
mcg_ds.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
mcg_ds.name = f"MCGD_{length}"
mcg_ds.category = "overlap"
+10 -7
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
def midpoint(close: Series, length: int = None, talib: bool = None, offset: int = None, **kwargs) -> Series:
def midpoint(
close: Series, length: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Midpoint
The Midpoint is the average of the rolling high and low of period length.
@@ -23,7 +26,7 @@ def midpoint(close: Series, length: int = None, talib: bool = None, offset: int
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 2
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
close = verify_series(close, max(length, min_periods))
@@ -32,7 +35,7 @@ def midpoint(close: Series, length: int = None, talib: bool = None, offset: int
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import MIDPOINT
midpoint = MIDPOINT(close, length)
@@ -45,13 +48,13 @@ def midpoint(close: Series, length: int = None, talib: bool = None, offset: int
if offset != 0:
midpoint = midpoint.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
midpoint.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
midpoint.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
midpoint.name = f"MIDPOINT_{length}"
midpoint.category = "overlap"
+10 -8
View File
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
def midprice(high: Series, low: Series, length: int = None, talib: bool = None, offset: int = None,
**kwargs) -> Series:
def midprice(
high: Series, low: Series, length: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Midprice
The Midprice is the average of the rolling high and low of period length.
@@ -25,7 +27,7 @@ def midprice(high: Series, low: Series, length: int = None, talib: bool = None,
Returns:
pd.Series: New feature generated.
"""
# Validate arguments
# Validate
length = int(length) if length and length > 0 else 2
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
_length = max(length, min_periods)
@@ -36,7 +38,7 @@ def midprice(high: Series, low: Series, length: int = None, talib: bool = None,
if high is None or low is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import MIDPRICE
midprice = MIDPRICE(high, low, length)
@@ -49,13 +51,13 @@ def midprice(high: Series, low: Series, length: int = None, talib: bool = None,
if offset != 0:
midprice = midprice.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
midprice.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
midprice.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
midprice.name = f"MIDPRICE_{length}"
midprice.category = "overlap"
+12 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def ohlc4(open_: Series, high: Series, low: Series, close: Series, offset: int = None, **kwargs) -> Series:
def ohlc4(
open_: Series, high: Series, low: Series, close: Series,
offset: int = None, **kwargs
) -> Series:
"""OHLC4
OHLC4 is the average of open, high, low and close.
@@ -18,21 +21,24 @@ def ohlc4(open_: Series, high: Series, low: Series, close: Series, offset: int =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
open_ = verify_series(open_)
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
offset = get_offset(offset)
# Calculate Result
ohlc4 = 0.25 * (open_ + high + low + close)
# Calculate
ohlc4 = Series(
0.25 * (open_.values + high.values + low.values + close.values),
index=close.index
)
# Offset
if offset != 0:
ohlc4 = ohlc4.shift(offset)
# Name & Category
# Name and Category
ohlc4.name = "OHLC4"
ohlc4.category = "overlap"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, pascals_triangle, verify_series, weights
from pandas import Series
from pandas_ta.utils import get_offset, pascals_triangle, verify_series, weights
def pwma(close: Series, length: int = None, asc: bool = None, offset: bool = None, **kwargs) -> Series:
def pwma(
close: Series, length: int = None, asc: bool = None,
offset: bool = None, **kwargs
) -> Series:
"""Pascal's Weighted Moving Average (PWMA)
Pascal's Weighted Moving Average is similar to a symmetric triangular window
@@ -24,7 +27,7 @@ def pwma(close: Series, length: int = None, asc: bool = None, offset: bool = Non
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
asc = asc if asc else True
close = verify_series(close, length)
@@ -32,7 +35,7 @@ def pwma(close: Series, length: int = None, asc: bool = None, offset: bool = Non
if close is None: return
# Calculate Result
# Calculate
triangle = pascals_triangle(n=length - 1, weighted=True)
pwma = close.rolling(length, min_periods=length).apply(weights(triangle), raw=True)
@@ -40,13 +43,13 @@ def pwma(close: Series, length: int = None, asc: bool = None, offset: bool = Non
if offset != 0:
pwma = pwma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
pwma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
pwma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
pwma.name = f"PWMA_{length}"
pwma.category = "overlap"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def rma(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def rma(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""wildeR's Moving Average (RMA)
The WildeR's Moving Average is simply an Exponential Moving Average (EMA) with
@@ -25,7 +28,7 @@ def rma(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
alpha = (1.0 / length) if length > 0 else 0.5
close = verify_series(close, length)
@@ -33,20 +36,20 @@ def rma(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
if close is None: return
# Calculate Result
# Calculate
rma = close.ewm(alpha=alpha, min_periods=length).mean()
# Offset
if offset != 0:
rma = rma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
rma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
rma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
rma.name = f"RMA_{length}"
rma.category = "overlap"
+10 -8
View File
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
from numpy import pi as npPi
from numpy import sin as npSin
from numpy import pi, sin
from pandas import Series
from pandas_ta.utils import get_offset, verify_series, weights
def sinwma(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def sinwma(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Sine Weighted Moving Average (SWMA)
A weighted average using sine cycles. The middle term(s) of the average have the
@@ -27,15 +29,15 @@ def sinwma(close: Series, length: int = None, offset: int = None, **kwargs) -> S
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 14
close = verify_series(close, length)
offset = get_offset(offset)
if close is None: return
# Calculate Result
sines = Series([npSin((i + 1) * npPi / (length + 1)) for i in range(0, length)])
# Calculate
sines = Series([sin((i + 1) * pi / (length + 1)) for i in range(0, length)])
w = sines / sines.sum()
sinwma = close.rolling(length, min_periods=length).apply(weights(w), raw=True)
@@ -44,13 +46,13 @@ def sinwma(close: Series, length: int = None, offset: int = None, **kwargs) -> S
if offset != 0:
sinwma = sinwma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
sinwma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
sinwma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
sinwma.name = f"SINWMA_{length}"
sinwma.category = "overlap"
+12 -9
View File
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
from numpy import nan as npNaN
from pandas_ta.overlap.ma import ma
from numpy import nan
from pandas import Series
from pandas_ta.ma import ma
from pandas_ta.utils import get_offset, verify_series
def smma(close: Series, length: int = None, mamode: str = None, talib: bool = None, offset: int = None,
**kwargs) -> Series:
def smma(
close: Series, length: int = None,
mamode: str = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""SMoothed Moving Average (SMMA)
The SMoothed Moving Average (SMMA) is bootstrapped by default with a Simple
@@ -37,7 +40,7 @@ def smma(close: Series, length: int = None, mamode: str = None, talib: bool = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 7
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
close = verify_series(close, max(length, min_periods))
@@ -47,10 +50,10 @@ def smma(close: Series, length: int = None, mamode: str = None, talib: bool = No
if close is None: return
# Calculate Result
# Calculate
m = close.size
smma = close.copy()
smma[:length - 1] = npNaN
smma[:length - 1] = nan
smma.iloc[length - 1] = ma(mamode, close[0:length], length=length, talib=mode_tal).iloc[-1]
for i in range(length, m):
@@ -60,13 +63,13 @@ def smma(close: Series, length: int = None, mamode: str = None, talib: bool = No
if offset != 0:
smma = smma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
smma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
smma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
smma.name = f"SMMA_{length}"
smma.category = "overlap"
+16 -11
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from pandas_ta import np, pd
from numpy import copy, cos, exp, ndarray
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
try:
@@ -9,12 +10,12 @@ except ImportError:
@njit
def np_ssf3(x: np.ndarray, n: int, pi: float, sqrt3: float):
def np_ssf3(x: ndarray, n: int, pi: float, sqrt3: float):
"""John F. Ehler's Super Smoother Filter by Everget (3 poles), Tradingview
https://www.tradingview.com/script/VdJy0yBJ-Ehlers-Super-Smoother-Filter/"""
m, result = x.size, np.copy(x)
a = np.exp(-pi / n)
b = 2 * a * np.cos(-pi * sqrt3 / n)
m, result = x.size, copy(x)
a = exp(-pi / n)
b = 2 * a * cos(-pi * sqrt3 / n)
c = a * a
d4 = c * c
@@ -29,7 +30,11 @@ def np_ssf3(x: np.ndarray, n: int, pi: float, sqrt3: float):
return result
def ssf3(close, length=None, pi=None, sqrt3=None, offset=None, **kwargs):
def ssf3(
close: Series, length: int = None,
pi: float = None, sqrt3: float = None,
offset=None, **kwargs
):
"""Ehler's 3 Pole Super Smoother Filter (SSF) © 2013
John F. Ehlers's solution to reduce lag and remove aliasing noise with his
@@ -62,7 +67,7 @@ def ssf3(close, length=None, pi=None, sqrt3=None, offset=None, **kwargs):
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if isinstance(length, int) and length > 0 else 20
pi = float(pi) if isinstance(pi, float) and pi > 0 else 3.14159
sqrt3 = float(sqrt3) if isinstance(sqrt3, float) and sqrt3 > 0 else 1.732
@@ -71,22 +76,22 @@ def ssf3(close, length=None, pi=None, sqrt3=None, offset=None, **kwargs):
if close is None: return
# Calculate Result
# Calculate
np_close = close.values
ssf = np_ssf3(np_close, length, pi, sqrt3)
ssf = pd.Series(ssf, index=close.index)
ssf = Series(ssf, index=close.index)
# Offset
if offset != 0:
ssf = ssf.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
ssf.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
ssf.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
ssf.name = f"SSF3_{length}"
ssf.category = "overlap"
+12 -10
View File
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
from numpy import nan as npNaN
from numpy import nan
from pandas import DataFrame, Series
from pandas_ta.overlap import hl2
from pandas_ta.volatility import atr
from pandas_ta.utils import get_offset, verify_series
from pandas_ta.volatility import atr
def supertrend(high: Series, low: Series, close: Series, length: int = None, multiplier: float = None,
offset: int = None, **kwargs) -> DataFrame:
def supertrend(
high: Series, low: Series, close: Series,
length: int = None, multiplier: float = None,
offset: int = None, **kwargs
) -> DataFrame:
"""Supertrend (supertrend)
Supertrend is an overlap indicator. It is used to help identify trend
@@ -33,7 +36,7 @@ def supertrend(high: Series, low: Series, close: Series, length: int = None, mul
Returns:
pd.DataFrame: SUPERT (trend), SUPERTd (direction), SUPERTl (long), SUPERTs (short) columns.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 7
multiplier = float(multiplier) if multiplier and multiplier > 0 else 3.0
high = verify_series(high, length)
@@ -43,10 +46,10 @@ def supertrend(high: Series, low: Series, close: Series, length: int = None, mul
if high is None or low is None or close is None: return
# Calculate Results
# Calculate
m = close.size
dir_, trend = [1] * m, [0] * m
long, short = [npNaN] * m, [npNaN] * m
long, short = [nan] * m, [nan] * m
hl2_ = hl2(high, low)
matr = multiplier * atr(high, low, close, length)
@@ -70,7 +73,6 @@ def supertrend(high: Series, low: Series, close: Series, length: int = None, mul
else:
trend[i] = short[i] = upperband.iloc[i]
# Prepare DataFrame to return
_props = f"_{length}_{multiplier}"
df = DataFrame({
f"SUPERT{_props}": trend,
@@ -82,11 +84,11 @@ def supertrend(high: Series, low: Series, close: Series, length: int = None, mul
df.name = f"SUPERT{_props}"
df.category = "overlap"
# Apply offset if needed
# Offset
if offset != 0:
df = df.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
df.fillna(kwargs["fillna"], inplace=True)
+9 -9
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, symmetric_triangle, verify_series, weights
from pandas import Series
from pandas_ta.utils import get_offset, symmetric_triangle, verify_series, weights
def swma(close: Series, length: int = None, asc: bool = None, offset: int = None, **kwargs) -> Series:
def swma(
close: Series, length: int = None, asc: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Symmetric Weighted Moving Average (SWMA)
Symmetric Weighted Moving Average where weights are based on a symmetric
@@ -27,31 +30,28 @@ def swma(close: Series, length: int = None, asc: bool = None, offset: int = None
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
# min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
asc = asc if asc else True
close = verify_series(close, length)
offset = get_offset(offset)
if close is None: return
# Calculate Result
# Calculate
triangle = symmetric_triangle(length, weighted=True)
swma = close.rolling(length, min_periods=length).apply(weights(triangle), raw=True)
# swma = close.rolling(length).apply(weights(triangle), raw=True)
# Offset
if offset != 0:
swma = swma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
swma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
swma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
swma.name = f"SWMA_{length}"
swma.category = "overlap"
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from .ema import ema
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
from .ema import ema
def t3(close: Series, length: int = None, a: float = None, talib: bool = None, offset: int = None, **kwargs) -> Series:
def t3(
close: Series, length: int = None, a: float = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Tim Tillson's T3 Moving Average (T3)
Tim Tillson's T3 Moving Average is considered a smoother and more responsive
@@ -31,7 +34,7 @@ def t3(close: Series, length: int = None, a: float = None, talib: bool = None, o
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
a = float(a) if a and a > 0 and a < 1 else 0.7
close = verify_series(close, length)
@@ -40,7 +43,7 @@ def t3(close: Series, length: int = None, a: float = None, talib: bool = None, o
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import T3
t3 = T3(close, length, a)
@@ -62,13 +65,13 @@ def t3(close: Series, length: int = None, a: float = None, talib: bool = None, o
if offset != 0:
t3 = t3.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
t3.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
t3.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
t3.name = f"T3_{length}_{a}"
t3.category = "overlap"
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from .ema import ema
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
from .ema import ema
def tema(close: Series, length: int = None, talib: bool = None, offset: int = None, **kwargs) -> Series:
def tema(
close: Series, length: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Triple Exponential Moving Average (TEMA)
A less laggy Exponential Moving Average.
@@ -29,7 +32,7 @@ def tema(close: Series, length: int = None, talib: bool = None, offset: int = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
offset = get_offset(offset)
@@ -37,7 +40,7 @@ def tema(close: Series, length: int = None, talib: bool = None, offset: int = No
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import TEMA
tema = TEMA(close, length)
@@ -51,13 +54,13 @@ def tema(close: Series, length: int = None, talib: bool = None, offset: int = No
if offset != 0:
tema = tema.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
tema.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
tema.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
tema.name = f"TEMA_{length}"
tema.category = "overlap"
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from .sma import sma
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
from .sma import sma
def trima(close: Series, length: int = None, talib: bool = None, offset: int = None, **kwargs) -> Series:
def trima(
close: Series, length: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Triangular Moving Average (TRIMA)
A weighted moving average where the shape of the weights are triangular and the
@@ -31,7 +34,7 @@ def trima(close: Series, length: int = None, talib: bool = None, offset: int = N
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
offset = get_offset(offset)
@@ -39,7 +42,7 @@ def trima(close: Series, length: int = None, talib: bool = None, offset: int = N
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import TRIMA
trima = TRIMA(close, length)
@@ -52,13 +55,13 @@ def trima(close: Series, length: int = None, talib: bool = None, offset: int = N
if offset != 0:
trima = trima.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
trima.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
trima.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
trima.name = f"TRIMA_{length}"
trima.category = "overlap"
+25 -21
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from numpy import nan as npNaN
from numpy import nan
from pandas import Series
from pandas_ta.utils import get_drift, get_offset, verify_series
def vidya(close: Series, length: int = None, drift: int = None, offset: int = None, **kwargs) -> Series:
def vidya(
close: Series, length: int = None, drift: int = None,
offset: int = None, **kwargs
) -> Series:
"""Variable Index Dynamic Average (VIDYA)
Variable Index Dynamic Average (VIDYA) was developed by Tushar Chande. It is
@@ -32,7 +35,7 @@ def vidya(close: Series, length: int = None, drift: int = None, offset: int = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 14
close = verify_series(close, length)
drift = get_drift(drift)
@@ -40,41 +43,42 @@ def vidya(close: Series, length: int = None, drift: int = None, offset: int = No
if close is None: return
def _cmo(source: Series, n:int , d: int):
"""Chande Momentum Oscillator (CMO) Patch
For some reason: from pandas_ta.momentum import cmo causes
pandas_ta.momentum.coppock to not be able to import it's
wma like from pandas_ta.overlap import wma?
Weird Circular TypeError!?!
"""
mom = source.diff(d)
positive = mom.copy().clip(lower=0)
negative = mom.copy().clip(upper=0).abs()
pos_sum = positive.rolling(n).sum()
neg_sum = negative.rolling(n).sum()
return (pos_sum - neg_sum) / (pos_sum + neg_sum)
# Calculate Result
# Calculate
m = close.size
alpha = 2 / (length + 1)
abs_cmo = _cmo(close, length, drift).abs()
vidya = Series(0, index=close.index)
for i in range(length, m):
vidya.iloc[i] = alpha * abs_cmo.iloc[i] * close.iloc[i] + vidya.iloc[i - 1] * (1 - alpha * abs_cmo.iloc[i])
vidya.replace({0: npNaN}, inplace=True)
vidya.replace({0: nan}, inplace=True)
# Offset
if offset != 0:
vidya = vidya.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
vidya.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
vidya.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
vidya.name = f"VIDYA_{length}"
vidya.category = "overlap"
return vidya
def _cmo(source: Series, n:int , d: int):
"""Chande Momentum Oscillator (CMO) Patch
For some reason: from pandas_ta.momentum import cmo causes
pandas_ta.momentum.coppock to not be able to import it's
wma like from pandas_ta.overlap import wma?
Weird Circular TypeError!?
"""
mom = source.diff(d)
positive = mom.copy().clip(lower=0)
negative = mom.copy().clip(upper=0).abs()
pos_sum = positive.rolling(n).sum()
neg_sum = negative.rolling(n).sum()
return (pos_sum - neg_sum) / (pos_sum + neg_sum)
+11 -8
View File
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
from .hlc3 import hlc3
from pandas_ta.utils import get_offset, is_datetime_ordered, verify_series
from pandas import Series
from pandas_ta.overlap import hlc3
from pandas_ta.utils import get_offset, is_datetime_ordered, verify_series
def vwap(high: Series, low: Series, close: Series, volume: Series, anchor: str = None, offset: int = None,
**kwargs) -> Series:
def vwap(
high: Series, low: Series, close: Series, volume: Series,
anchor: str = None,
offset: int = None, **kwargs
) -> Series:
"""Volume Weighted Average Price (VWAP)
The Volume Weighted Average Price that measures the average typical price
@@ -35,7 +38,7 @@ def vwap(high: Series, low: Series, close: Series, volume: Series, anchor: str =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
@@ -49,7 +52,7 @@ def vwap(high: Series, low: Series, close: Series, volume: Series, anchor: str =
if not is_datetime_ordered(typical_price):
print(f"[!] VWAP price series is not datetime ordered. Results may not be as expected.")
# Calculate Result
# Calculate
wp = typical_price * volume
vwap = wp.groupby(wp.index.to_period(anchor)).cumsum()
vwap /= volume.groupby(volume.index.to_period(anchor)).cumsum()
@@ -58,13 +61,13 @@ def vwap(high: Series, low: Series, close: Series, volume: Series, anchor: str =
if offset != 0:
vwap = vwap.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
vwap.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
vwap.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
vwap.name = f"VWAP_{anchor}"
vwap.category = "overlap"
+10 -7
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from .sma import sma
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.overlap import sma
from pandas_ta.utils import get_offset, verify_series
def vwma(close: Series, volume: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def vwma(
close: Series, volume: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Volume Weighted Moving Average (VWMA)
Volume Weighted Moving Average.
@@ -25,7 +28,7 @@ def vwma(close: Series, volume: Series, length: int = None, offset: int = None,
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
close = verify_series(close, length)
volume = verify_series(volume, length)
@@ -33,7 +36,7 @@ def vwma(close: Series, volume: Series, length: int = None, offset: int = None,
if close is None or volume is None: return
# Calculate Result
# Calculate
pv = close * volume
vwma = sma(close=pv, length=length) / sma(close=volume, length=length)
@@ -41,13 +44,13 @@ def vwma(close: Series, volume: Series, length: int = None, offset: int = None,
if offset != 0:
vwma = vwma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
vwma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
vwma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
vwma.name = f"VWMA_{length}"
vwma.category = "overlap"
+11 -8
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
def wcp(high: Series, low: Series, close: Series, talib: bool = None, offset: int = None, **kwargs) -> Series:
def wcp(
high: Series, low: Series, close: Series, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Weighted Closing Price (WCP)
Weighted Closing Price is the weighted price given: high, low
@@ -28,31 +31,31 @@ def wcp(high: Series, low: Series, close: Series, talib: bool = None, offset: in
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
offset = get_offset(offset)
mode_tal = bool(talib) if isinstance(talib, bool) else True
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import WCLPRICE
wcp = WCLPRICE(high, low, close)
else:
wcp = (high + low + 2 * close) / 4
wcp = Series((high.values + low.values + 2 * close.values), index=close.index)
# Offset
if offset != 0:
wcp = wcp.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
wcp.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
wcp.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
wcp.name = "WCP"
wcp.category = "overlap"
+8 -10
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from numpy import arange, dot
from pandas import Series
from pandas_ta import Imports
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
@@ -29,7 +30,7 @@ def wma(close: Series, length: int = None, asc: bool = None, talib: bool = None,
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
asc = asc if asc else True
close = verify_series(close, length)
@@ -38,21 +39,18 @@ def wma(close: Series, length: int = None, asc: bool = None, talib: bool = None,
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import WMA
wma = WMA(close, length)
else:
from numpy import arange as npArange
from numpy import dot as npDot
total_weight = 0.5 * length * (length + 1)
weights_ = Series(npArange(1, length + 1))
weights_ = Series(arange(1, length + 1))
weights = weights_ if asc else weights_[::-1]
def linear(w):
def _compute(x):
return npDot(x, w) / total_weight
return dot(x, w) / total_weight
return _compute
close_ = close.rolling(length, min_periods=length)
@@ -62,13 +60,13 @@ def wma(close: Series, length: int = None, asc: bool = None, talib: bool = None,
if offset != 0:
wma = wma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
wma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
wma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
wma.name = f"WMA_{length}"
wma.category = "overlap"
+52 -11
View File
@@ -1,13 +1,29 @@
# -*- coding: utf-8 -*-
# from . import (
# dema, ema, hma, linreg, rma, sma, swma, t3, tema, trima, vidya, wma
# )
from pandas_ta.overlap import ma
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
from .dema import dema
from .ema import ema
from .fwma import fwma
from .hma import hma
from .linreg import linreg
from .midpoint import midpoint
from .pwma import pwma
from .rma import rma
from .sinwma import sinwma
from .sma import sma
from .ssf import ssf
from .swma import swma
from .t3 import t3
from .tema import tema
from .trima import trima
from .vidya import vidya
from .wma import wma
def zlma(close: Series, length: int = None, mamode: str = None, offset: int = None, **kwargs) -> Series:
def zlma(
close: Series, length: int = None, mamode: str = None,
offset: int = None, **kwargs
) -> Series:
"""Zero Lag Moving Average (ZLMA)
The Zero Lag Moving Average attempts to eliminate the lag associated
@@ -29,7 +45,7 @@ def zlma(close: Series, length: int = None, mamode: str = None, offset: int = No
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
mamode = mamode.lower() if isinstance(mamode, str) else "ema"
close = verify_series(close, length)
@@ -37,22 +53,47 @@ def zlma(close: Series, length: int = None, mamode: str = None, offset: int = No
if close is None: return
# Calculate Result
# Calculate
lag = int(0.5 * (length - 1))
close_ = 2 * close - close.shift(lag)
zlma = ma(mamode, close_, length=length, **kwargs)
kwargs.update({"close": close_})
kwargs.update({"length": length})
# Not ideal but it works. Submit a PR for a better solution. =)
# This design pattern is undesirable
def _ma(**kwargs):
if mamode == "dema": return dema(**kwargs)
elif mamode == "fwma": return fwma(**kwargs)
elif mamode == "hma": return hma(**kwargs)
elif mamode == "linreg": return linreg(**kwargs)
elif mamode == "midpoint": return midpoint(**kwargs)
elif mamode == "pwma": return pwma(**kwargs)
elif mamode == "rma": return rma(**kwargs)
elif mamode == "sinwma": return sinwma(**kwargs)
elif mamode == "sma": return sma(**kwargs)
elif mamode == "ssf": return ssf(**kwargs)
elif mamode == "swma": return swma(**kwargs)
elif mamode == "t3": return t3(**kwargs)
elif mamode == "tema": return tema(**kwargs)
elif mamode == "trima": return trima(**kwargs)
elif mamode == "vidya": return vidya(**kwargs)
elif mamode == "wma": return wma(**kwargs)
else: return ema(**kwargs)
zlma = _ma(**kwargs)
# Offset
if offset != 0:
zlma = zlma.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
zlma.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
zlma.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
zlma.name = f"ZL_{zlma.name}"
zlma.category = "overlap"
+9 -9
View File
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
from numpy import log as nplog
from numpy import seterr
from numpy import log, seterr
from pandas import DataFrame, Series
from pandas_ta.utils import get_offset, verify_series
def drawdown(close: Series, offset: int = None, **kwargs) -> DataFrame:
def drawdown(
close: Series, offset: int = None, **kwargs
) -> DataFrame:
"""Drawdown (DD)
Drawdown is a peak-to-trough decline during a specific period for an investment,
@@ -26,18 +27,18 @@ def drawdown(close: Series, offset: int = None, **kwargs) -> DataFrame:
Returns:
pd.DataFrame: drawdown, drawdown percent, drawdown log columns
"""
# Validate Arguments
# Validate
close = verify_series(close)
offset = get_offset(offset)
# Calculate Result
# Calculate
max_close = close.cummax()
dd = max_close - close
dd_pct = 1 - (close / max_close)
_np_err = seterr()
seterr(divide="ignore", invalid="ignore")
dd_log = nplog(max_close) - nplog(close)
dd_log = log(max_close) - log(close)
seterr(divide=_np_err["divide"], invalid=_np_err["invalid"])
# Offset
@@ -46,7 +47,7 @@ def drawdown(close: Series, offset: int = None, **kwargs) -> DataFrame:
dd_pct = dd_pct.shift(offset)
dd_log = dd_log.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
dd.fillna(kwargs["fillna"], inplace=True)
dd_pct.fillna(kwargs["fillna"], inplace=True)
@@ -56,13 +57,12 @@ def drawdown(close: Series, offset: int = None, **kwargs) -> DataFrame:
dd_pct.fillna(method=kwargs["fill_method"], inplace=True)
dd_log.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
# Name and Category
dd.name = "DD"
dd_pct.name = f"{dd.name}_PCT"
dd_log.name = f"{dd.name}_LOG"
dd.category = dd_pct.category = dd_log.category = "performance"
# Prepare DataFrame to return
data = {dd.name: dd, dd_pct.name: dd_pct, dd_log.name: dd_log}
df = DataFrame(data)
df.name = dd.name
+15 -10
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from numpy import log as nplog
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from numpy import log, nan, roll
from pandas_ta.utils import get_offset, verify_series
def log_return(close: Series, length: int = None, cumulative: bool = None, offset: int = None, **kwargs) -> Series:
def log_return(
close: Series, length: int = None, cumulative: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Log Return
Calculates the logarithmic return of a Series.
@@ -26,7 +29,7 @@ def log_return(close: Series, length: int = None, cumulative: bool = None, offse
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 1
cumulative = bool(cumulative) if cumulative is not None and cumulative else False
close = verify_series(close, length)
@@ -34,24 +37,26 @@ def log_return(close: Series, length: int = None, cumulative: bool = None, offse
if close is None: return
# Calculate Result
# Calculate
np_close = close.values
if cumulative:
# log_return = nplog(close).diff(length).cumsum()
log_return = nplog(close / close.iloc[0])
r = np_close / np_close[0]
else:
log_return = nplog(close / close.shift(length)) # nplog(close).diff(length)
r = np_close / roll(np_close, length)
r[:length] = nan
log_return = Series(log(r), index=close.index)
# Offset
if offset != 0:
log_return = log_return.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
log_return.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
log_return.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
log_return.name = f"{'CUM' if cumulative else ''}LOGRET_{length}"
log_return.category = "performance"
+16 -10
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from numpy import nan, roll
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def percent_return(close: Series, length: int = None, cumulative: bool = None, offset: int = None,
**kwargs) -> Series:
def percent_return(
close: Series, length: int = None, cumulative: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Percent Return
Calculates the percent return of a Series.
@@ -26,7 +29,7 @@ def percent_return(close: Series, length: int = None, cumulative: bool = None, o
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 1
cumulative = bool(cumulative) if cumulative is not None and cumulative else False
close = verify_series(close, length)
@@ -34,23 +37,26 @@ def percent_return(close: Series, length: int = None, cumulative: bool = None, o
if close is None: return
# Calculate Result
if cumulative:
pct_return = (close / close.iloc[0]) - 1
# Calculate
np_close = close.values
if True:#cumulative:
pr = (np_close / np_close[0]) - 1
else:
pct_return = close.pct_change(length) # (close / close.shift(length)) - 1
pr = (np_close / roll(np_close, length)) - 1
pr[:length] = nan
pct_return = Series(pr, index=close.index)
# Offset
if offset != 0:
pct_return = pct_return.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
pct_return.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
pct_return.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
pct_return.name = f"{'CUM' if cumulative else ''}PCTRET_{length}"
pct_return.category = "performance"
+11 -8
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from numpy import log as npLog
from pandas_ta.utils import get_offset, verify_series
from numpy import log
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def entropy(close: Series, length: int = None, base: float = None, offset: int = None, **kwargs) -> Series:
def entropy(
close: Series, length: int = None, base: float = None,
offset: int = None, **kwargs
) -> Series:
"""Entropy (ENTP)
Introduced by Claude Shannon in 1948, entropy measures the unpredictability
@@ -27,7 +30,7 @@ def entropy(close: Series, length: int = None, base: float = None, offset: int =
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 10
base = float(base) if base and base > 0 else 2.0
close = verify_series(close, length)
@@ -35,21 +38,21 @@ def entropy(close: Series, length: int = None, base: float = None, offset: int =
if close is None: return
# Calculate Result
# Calculate
p = close / close.rolling(length).sum()
entropy = (-p * npLog(p) / npLog(base)).rolling(length).sum()
entropy = (-p * log(p) / log(base)).rolling(length).sum()
# Offset
if offset != 0:
entropy = entropy.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
entropy.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
entropy.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
entropy.name = f"ENTP_{length}"
entropy.category = "statistics"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def kurtosis(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def kurtosis(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Rolling Kurtosis
Calculates the Kurtosis over a rolling period.
@@ -20,7 +23,7 @@ def kurtosis(close: Series, length: int = None, offset: int = None, **kwargs) ->
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 30
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
close = verify_series(close, max(length, min_periods))
@@ -28,20 +31,20 @@ def kurtosis(close: Series, length: int = None, offset: int = None, **kwargs) ->
if close is None: return
# Calculate Result
# Calculate
kurtosis = close.rolling(length, min_periods=min_periods).kurt()
# Offset
if offset != 0:
kurtosis = kurtosis.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
kurtosis.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
kurtosis.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
kurtosis.name = f"KURT_{length}"
kurtosis.category = "statistics"
+11 -8
View File
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from numpy import fabs as npfabs
from pandas_ta.utils import get_offset, verify_series
from numpy import fabs
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def mad(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def mad(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Rolling Mean Absolute Deviation
Calculates the Mean Absolute Deviation over a rolling period.
@@ -21,7 +24,7 @@ def mad(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 30
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
close = verify_series(close, max(length, min_periods))
@@ -29,10 +32,10 @@ def mad(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
if close is None: return
# Calculate Result
# Calculate
def mad_(series):
"""Mean Absolute Deviation"""
return npfabs(series - series.mean()).mean()
return fabs(series - series.mean()).mean()
mad = close.rolling(length, min_periods=min_periods).apply(mad_, raw=True)
@@ -40,13 +43,13 @@ def mad(close: Series, length: int = None, offset: int = None, **kwargs) -> Seri
if offset != 0:
mad = mad.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
mad.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
mad.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
mad.name = f"MAD_{length}"
mad.category = "statistics"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def median(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def median(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Rolling Median
Calculates the Median over a rolling period. Sibling of a Simple Moving Average.
@@ -23,7 +26,7 @@ def median(close: Series, length: int = None, offset: int = None, **kwargs) -> S
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 30
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
close = verify_series(close, max(length, min_periods))
@@ -31,20 +34,20 @@ def median(close: Series, length: int = None, offset: int = None, **kwargs) -> S
if close is None: return
# Calculate Result
# Calculate
median = close.rolling(length, min_periods=min_periods).median()
# Offset
if offset != 0:
median = median.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
median.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
median.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
median.name = f"MEDIAN_{length}"
median.category = "statistics"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def quantile(close: Series, length: int = None, q: float = None, offset: int = None, **kwargs) -> Series:
def quantile(
close: Series, length: int = None, q: float = None,
offset: int = None, **kwargs
) -> Series:
"""Rolling Quantile
Calculates the Quantile over a rolling period.
@@ -21,7 +24,7 @@ def quantile(close: Series, length: int = None, q: float = None, offset: int = N
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 30
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
q = float(q) if q and q > 0 and q < 1 else 0.5
@@ -30,20 +33,20 @@ def quantile(close: Series, length: int = None, q: float = None, offset: int = N
if close is None: return
# Calculate Result
# Calculate
quantile = close.rolling(length, min_periods=min_periods).quantile(q)
# Offset
if offset != 0:
quantile = quantile.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
quantile.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
quantile.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
quantile.name = f"QTL_{length}_{q}"
quantile.category = "statistics"
+9 -6
View File
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from pandas_ta.utils import get_offset, verify_series
from pandas import Series
from pandas_ta.utils import get_offset, verify_series
def skew(close: Series, length: int = None, offset: int = None, **kwargs) -> Series:
def skew(
close: Series, length: int = None,
offset: int = None, **kwargs
) -> Series:
"""Rolling Skew
Calculates the Skew over a rolling period.
@@ -20,7 +23,7 @@ def skew(close: Series, length: int = None, offset: int = None, **kwargs) -> Ser
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if length and length > 0 else 30
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
close = verify_series(close, max(length, min_periods))
@@ -28,20 +31,20 @@ def skew(close: Series, length: int = None, offset: int = None, **kwargs) -> Ser
if close is None: return
# Calculate Result
# Calculate
skew = close.rolling(length, min_periods=min_periods).skew()
# Offset
if offset != 0:
skew = skew.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
skew.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
skew.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
skew.name = f"SKEW_{length}"
skew.category = "statistics"
+14 -11
View File
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
from numpy import sqrt as npsqrt
from .variance import variance
from pandas_ta import Imports
from pandas_ta.utils import get_offset, verify_series
from numpy import sqrt
from pandas import Series
from pandas_ta.maps import Imports
from pandas_ta.utils import get_offset, verify_series
from .variance import variance
def stdev(close: Series, length: int = None, ddof: int = None, talib: bool = None, offset: int = None,
**kwargs) -> Series:
def stdev(
close: Series, length: int = None,
ddof: int = None, talib: bool = None,
offset: int = None, **kwargs
) -> Series:
"""Rolling Standard Deviation
Calculates the Standard Deviation over a rolling period.
@@ -30,7 +33,7 @@ def stdev(close: Series, length: int = None, ddof: int = None, talib: bool = Non
Returns:
pd.Series: New feature generated.
"""
# Validate Arguments
# Validate
length = int(length) if isinstance(length, int) and length > 0 else 30
ddof = int(ddof) if isinstance(ddof, int) and ddof >= 0 and ddof < length else 1
close = verify_series(close, length)
@@ -39,24 +42,24 @@ def stdev(close: Series, length: int = None, ddof: int = None, talib: bool = Non
if close is None: return
# Calculate Result
# Calculate
if Imports["talib"] and mode_tal:
from talib import STDDEV
stdev = STDDEV(close, length)
else:
stdev = variance(close=close, length=length, ddof=ddof, talib=mode_tal).apply(npsqrt)
stdev = variance(close=close, length=length, ddof=ddof, talib=mode_tal).apply(sqrt)
# Offset
if offset != 0:
stdev = stdev.shift(offset)
# Handle fills
# Fill
if "fillna" in kwargs:
stdev.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
stdev.fillna(method=kwargs["fill_method"], inplace=True)
# Name & Category
# Name and Category
stdev.name = f"STDEV_{length}"
stdev.category = "statistics"

Some files were not shown because too many files have changed in this diff Show More