Merge branch 'development'

This commit is contained in:
Kevin Johnson
2020-08-13 15:46:25 -07:00
30 changed files with 2369 additions and 1422 deletions
+1 -2
View File
@@ -133,6 +133,7 @@ note.md
driver.py
doc.py
bt.ipynb
scratch.ipynb
ta_extension.ipynb
scratch.ipynb
kerasmodeller.ipynb
@@ -145,5 +146,3 @@ simple.ipynb
ta.json
# Indicator
pandas_ta/overlap/ft.py
pandas_ta/overlap/psar.py
+45 -18
View File
@@ -6,22 +6,44 @@
# __Technical Analysis Library in Python 3.7__
![Example Chart](/images/TA_Chart.png)
__Pandas Technical Analysis__ (Pandas TA) is an easy to use library that is built upon Python's Pandas library with more than 100 Indicators and Utility functions. These indicators are commonly used for financial time series datasets with columns or labels similar to: datetime, open, high, low, close, volume, et al. Many commonly used indicators are included, such as: _Simple Moving Average_ (*SMA*) _Moving Average Convergence Divergence_ (*MACD*), _Hull Exponential Moving Average_ (*HMA*), _Bollinger Bands_ (*BBANDS*), _On-Balance Volume_ (*OBV*), _Aroon & Aroon Oscillator_ (*AROON*) and more.
_Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that is built upon Python's Pandas library with more than 115 Indicators and Utility functions. These indicators are commonly used for financial time series datasets with columns or labels: datetime, open, high, low, close, volume, et al. Many commonly used indicators are included, such as: _Simple Moving Average_ (**sma**) _Moving Average Convergence Divergence_ (**macd**), _Hull Exponential Moving Average_ (**hma**), _Bollinger Bands_ (**bbands**), _On-Balance Volume_ (**obv**), _Aroon & Aroon Oscillator_ (**aroon**), _Squeeze_ (**squeeze**) and **many more**.
This version contains both the orignal code branch as well as a newly refactored branch with the option to use [Pandas DataFrame Extension](https://pandas.pydata.org/pandas-docs/stable/extending.html) mode.
All the indicators return a named Series or a DataFrame in uppercase underscore parameter format. For example, MACD(fast=12, slow=26, signal=9) will return a DataFrame with columns: ['MACD_12_26_9', 'MACDh_12_26_9', 'MACDs_12_26_9'].
**Pandas TA** has three different ways of processing Technical Indicators as described below. The **primary** requirement to run indicators in [Pandas DataFrame Extension](https://pandas.pydata.org/pandas-docs/stable/extending.html) mode, is that _open, high, low, close, volume_ are **lowercase**. Depending on the indicator, they either return a named Series or a DataFrame in uppercase underscore parameter format. For example, MACD(fast=12, slow=26, signal=9) will return a DataFrame with columns: ['MACD_12_26_9', 'MACDh_12_26_9', 'MACDs_12_26_9'].
## Pandas TA Issues, Ideas and Contributions
#### Thanks for trying **Pandas TA**!
Please take a moment to read **this** and the rest of this **README** before posting any issue.
* ### [Comments and Feedback](https://github.com/twopirllc/pandas-ta/issues)
* Have you read the rest of **this** document?
* Are you running the latest version?
* Have you tried the [Examples](https://github.com/twopirllc/pandas-ta/tree/master/examples/)?
* Did they help?
* What is missing?
* Could you help improve them?
* Did you know you can easily build _Custom Strategies_ with the **[Strategy](https://github.com/twopirllc/pandas-ta/blob/master/examples/PandasTA_Strategy_Examples.ipynb) Class**?
* Documentation needs improvement. Can you contribute?
* ### [Indicator or Feature Requests & Contributions](https://github.com/twopirllc/pandas-ta/issues)
* Please be as detailed and concise as possible. Links and screenshots and sometimes data samples are welcome.
* You want a new indicator not currently listed.
* You want an alternate version of an existing indicator.
* The indicator does not match another website, library, broker platform, language, et al.
* Can you contribute?
## __Features__
* Has 100+ indicators and utility functions.
* Option to use __multiprocessing__ when using df.ta.strategy(). See below.
* Example Jupyter Notebooks under the [examples](https://github.com/twopirllc/pandas-ta/tree/master/examples) directory, including how to create Custom Strategies using the new [__Strategy__ Class](https://github.com/twopirllc/pandas-ta/blob/master/examples/PandasTA_Strategy_Examples.ipynb)
* A new 'ta' method called 'strategy'. By default, it runs __all__ the indicators.
* Abbreviated Indicator names as listed below.
* Has 115+ indicators and utility functions.
* __Extended Pandas DataFrame__ as 'ta'.
* Indicators are correlation tested against the de facto [TA Lib](https://mrjbq7.github.io/ta-lib/) if they share common indicators.
* Example Jupyter Notebooks under the [examples](https://github.com/twopirllc/pandas-ta/tree/master/examples) directory, including how to create Custom Strategies using the new [__Strategy__ Class](https://github.com/twopirllc/pandas-ta/tree/master/examples/PandaTA_Strategy_Examples.ipynb)
* Option to use __multiprocessing__ when using df.ta.strategy(). See below.
* Easily add prefixes or suffixes or both to columns names.
* Categories similar to [TA-lib](https://github.com/mrjbq7/ta-lib/tree/master/docs/func_groups) and tightly correlated with TA Lib in testing.
* A new 'ta' method called 'strategy'. By default, it runs __all__ the indicators or equivalent ta.AllStrategy.
## __Recent Changes__
@@ -30,6 +52,7 @@ All the indicators return a named Series or a DataFrame in uppercase underscore
* Improved the calculation performance of indicators: _Exponential Moving Averagage_
and _Weighted Moving Average_.
* Removed internal core optimizations when running ```df.ta.strategy('all')``` with multiprocessing. See the ```ta.strategy()``` method for more details.
* __New Indicators:__ Kaufman's _Efficiency Ratio_ **er**, Johnson's _Pretty Good Oscillator_ **pgo**, _Elder Ray Index_ **eri**, Martin's _Ulcer Index_ **ui**, _Squeeze_ **squeeze** (John Carter's TTM **and** Lazybear's TradingView versions)
## What is a Pandas DataFrame Extension?
@@ -58,7 +81,7 @@ import pandas as pd
import pandas_ta as ta
# Load data
df = pd.read_csv('symbol.csv', sep=',')
df = pd.read_csv("path/symbol.csv", sep=",")
# Calculate Returns and append to the df DataFrame
df.ta.log_return(cumulative=True, append=True)
@@ -160,8 +183,8 @@ df.ta.mp = True
# Runs and appends all indicators to the current DataFrame by default
# The resultant DataFrame will be large.
df.ta.strategy()
# Or equivalently use name='all'
df.ta.strategy(name='all')
# Or equivalently use name="all"
df.ta.strategy(name="all")
# Use verbose if you want to make sure it is running.
df.ta.strategy(verbose=True)
@@ -176,7 +199,7 @@ df.ta.strategy(cores=2)
# Maybe you do not want certain indicators.
# Just exclude (a list of) them.
df.ta.strategy(exclude=['bop', 'mom', 'percent_return', 'wcp', 'pvi'], verbose=True)
df.ta.strategy(exclude=["bop", "mom", "percent_return", "wcp", "pvi"], verbose=True)
# Perhaps you want to use different values for indicators.
# This will run ALL indicators that have fast or slow as parameters.
@@ -262,7 +285,7 @@ time_series_in_order = df.ta.datetime_ordered
```python
# Set ta to default to an adjusted column, 'adj_close', overriding default 'close'
df.ta.adjusted = 'adj_close'
df.ta.adjusted = "adj_close"
df.ta.sma(length=10, append=True)
# To reset back to 'close', set adjusted back to None
@@ -276,7 +299,7 @@ df.ta.adjusted = None
* _Doji_: **cdl_doji**
* _Heikin-Ashi_: **ha**
## _Momentum_ (27)
## _Momentum_ (31)
* _Awesome Oscillator_: **ao**
* _Absolute Price Oscillator_: **apo**
@@ -287,12 +310,15 @@ df.ta.adjusted = None
* _Center of Gravity_: **cg**
* _Chande Momentum Oscillator_: **cmo**
* _Coppock Curve_: **coppock**
* _Efficiency Ratio_: **er**
* _Elder Ray Index_: **eri**
* _Fisher Transform_: **fisher**
* _Inertia_: **inertia**
* _KDJ_: **kdj**
* _KST Oscillator_: **kst**
* _Moving Average Convergence Divergence_: **macd**
* _Momentum_: **mom**
* _Pretty Good Oscillator_: **pgo**
* _Percentage Price Oscillator_: **ppo**
* _Psychological Line_: **psl**
* _Percentage Volume Oscillator_: **pvo**
@@ -300,6 +326,8 @@ df.ta.adjusted = None
* _Relative Strength Index_: **rsi**
* _Relative Vigor Index_: **rvgi**
* _Slope_: **slope**
* _Squeeze_: **squeeze**
* Default is John Carter's. Enable Lazybear's by ```lazybear=True```
* _Stochastic Oscillator_: **stoch**
* _Trix_: **trix**
* _True strength index_: **tsi**
@@ -403,7 +431,7 @@ Use parameter: cumulative=**True** for cumulative results.
* _Below Value_: **below_value**
* _Cross_: **cross**
## _Volatility_ (11)
## _Volatility_ (12)
* _Aberration_: **aberration**
* _Acceleration Bands_: **accbands**
@@ -416,6 +444,7 @@ Use parameter: cumulative=**True** for cumulative results.
* _Price Distance_: **pdist**
* _Relative Volatility Index_: **rvi**
* _True Range_: **true_range**
* _Ulcer Index_: **ui**
| _Average True Range_ (ATR) |
|:--------:|
@@ -450,7 +479,5 @@ Use parameter: cumulative=**True** for cumulative results.
# Inspiration
* TradingView: http://www.tradingview.com
* Original TA-LIB: http://ta-lib.org/
Please leave any comments, feedback, suggestions, or indicator requests.
* TradingView: http://www.tradingview.com
File diff suppressed because it is too large Load Diff
+919 -669
View File
File diff suppressed because one or more lines are too long
+76 -31
View File
@@ -8,6 +8,7 @@ from time import perf_counter
from typing import List
import pandas as pd
from numpy import ndarray as npndarray
from pandas_ta import categories
from pandas.core.base import PandasObject
@@ -21,21 +22,23 @@ from pandas_ta.volatility import *
from pandas_ta.volume import *
from pandas_ta.utils import *
version = ".".join(("0", "1", "78b"))
version = ".".join(("0", "1", "90b"))
# Dictionary of files for each category, used in df.ta.strategy()
Category = {name: category_files(name) for name in categories}
def mp_worker(args):
"""Multiprocessing Worker to handle different Methods."""
df, method, kwargs = args
if method != 'ichimoku':
if method != "ichimoku":
return df.ta(kind=method, **kwargs)
else:
return df.ta(kind=method, **kwargs)[0]
def finalize(method):
"""Adds Prefixes/Suffixes if given and Appends Results if True"""
@wraps(method)
def _wrapper(*class_methods, **method_kwargs):
cm = class_methods[0]
@@ -142,7 +145,7 @@ class BasePandasObject(PandasObject):
if len(df.columns) > 0:
self._df = df
else:
raise AttributeError(f" [X] No columns!")
raise AttributeError(f"[X] No columns!")
def __call__(self, kind, *args, **kwargs):
raise NotImplementedError()
@@ -355,41 +358,43 @@ class AnalysisIndicators(BasePandasObject):
return df.iloc[:,match[0]] if len(match) else print(NOT_FOUND)
def constants(self, append, lower_bound=-100, upper_bound=100, every=10):
def constants(self, append: bool, values: list):
"""Constants
Useful for creating indicator levels or if you need some constant value
easily added to your DataFrame.
Add or remove constants to the DataFrame easily with Numpy's arrays or
lists. Useful when you need easily accessible horizontal lines for
charting.
Add constant '1' to the DataFrame
>>> df.ta.constants(True, 1, 1, 1)
>>> df.ta.constants(True, [1])
Remove constant '1' to the DataFrame
>>> df.ta.constants(False, 1, 1, 1)
>>> df.ta.constants(False, [1])
Adding constants that range of constants from -4 to 4 inclusive
>>> df.ta.constants(True, -4, 4, 1)
Removing constants that range of constants from -4 to 4 inclusive
>>> df.ta.constants(False, -4, 4, 1)
Adding the constants for the charts
>>> import numpy as np
>>> chart_lines = np.append(np.arange(-4, 5, 1), np.arange(-100, 110, 10))
>>> df.ta.constants(True, chart_lines)
Removing some constants from the DataFrame
>>> df.ta.constants(False, np.array([-60, -40, 40, 60]))
Args:
append (bool): Default: None. If True, appends the range of constants to the
working DataFrame. If False, it removes the constant range from the working
DataFrame.
lower_bound (int): Default: -100. Lowest integer for the constant range.
upper_bound (int): Default: 100. Largest integer for the constant range.
every (int): Default: 10. How often to include a new constant.
append (bool): If True, appends a Numpy range of constants to the
working DataFrame. If False, it removes the constant range from
the working DataFrame. Default: None.
Returns:
Returns nothing to the user. Either adds or removes constant ranges from the
working DataFrame.
Returns the appended constants
Returns nothing to the user. Either adds or removes constant ranges
from the working DataFrame.
"""
levels = [x for x in range(lower_bound, upper_bound + 1) if x % every == 0]
if append:
for x in levels:
self._df[f'{x}'] = x
else:
for x in levels:
del self._df[f'{x}']
if isinstance(values, npndarray) or isinstance(values, list):
if append:
for x in values:
self._df[f"{x}"] = x
return self._df[self._df.columns[-len(values):]]
else:
for x in values:
del self._df[f"{x}"]
def indicators(self, **kwargs):
@@ -397,10 +402,10 @@ class AnalysisIndicators(BasePandasObject):
Args:
kwargs:
as_list (bool, optional): Default: False. When True, it returns a list
of the indicators. Helpful you want to filter out what you want to run.
exclude (list, optional): Default: None. The passed in list will be
excluded from the indicators list.
as_list (bool, optional): Default: False. When True, it
returns a list of the indicators.
exclude (list, optional): Default: None. The passed in list
will be excluded from the indicators list.
Returns:
Prints the list of indicators. If as_list=True, then a list.
@@ -619,6 +624,22 @@ class AnalysisIndicators(BasePandasObject):
result = coppock(close=close, length=length, fast=fast, slow=slow, offset=offset, **kwargs)
return result
@finalize
def er(self, close=None, length=None, drift=None, offset=None, **kwargs):
close = self._get_column(close, 'close')
result = er(close=close, length=length, drift=drift, offset=offset, **kwargs)
return result
@finalize
def eri(self, high=None, low=None, close=None, length=None, offset=None, **kwargs):
high = self._get_column(high, 'high')
low = self._get_column(low, 'low')
close = self._get_column(close, 'close')
result = eri(high=high, low=low, close=close, length=length, offset=offset, **kwargs)
return result
@finalize
def fisher(self, high=None, low=None, length=None, offset=None, **kwargs):
high = self._get_column(high, 'high')
@@ -671,6 +692,15 @@ class AnalysisIndicators(BasePandasObject):
result = mom(close=close, length=length, offset=offset, **kwargs)
return result
@finalize
def pgo(self, high=None, low=None, close=None, length=None, offset=None, **kwargs):
high = self._get_column(high, 'high')
low = self._get_column(low, 'low')
close = self._get_column(close, 'close')
result = pgo(high=high, low=low, close=close, length=length, offset=offset, **kwargs)
return result
@finalize
def ppo(self, close=None, fast=None, slow=None, scalar=None, offset=None, **kwargs):
close = self._get_column(close, 'close')
@@ -725,6 +755,15 @@ class AnalysisIndicators(BasePandasObject):
result = slope(close=close, length=length, offset=offset, **kwargs)
return result
@finalize
def squeeze(self, high=None, low=None, close=None, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs):
high = self._get_column(high, 'high')
low = self._get_column(low, 'low')
close = self._get_column(close, 'close')
result = squeeze(high=high, low=low, close=close, bb_length=bb_length, bb_std=bb_std, kc_length=kc_length, kc_scalar=kc_scalar, mom_length=mom_length, mom_smooth=mom_smooth, use_tr=use_tr, offset=offset, **kwargs)
return result
@finalize
def stoch(self, high=None, low=None, close=None, fast_k=None, slow_k=None, slow_d=None, offset=None, **kwargs):
high = self._get_column(high, 'high')
@@ -1356,6 +1395,12 @@ class AnalysisIndicators(BasePandasObject):
result = true_range(high=high, low=low, close=close, drift=drift, offset=offset, **kwargs)
return result
@finalize
def ui(self, close=None, length=None, scalar=None, offset=None, **kwargs):
close = self._get_column(close, 'close')
result = ui(close=close, length=length, scalar=scalar, offset=offset, **kwargs)
return result
# Volume Indicators
+4
View File
@@ -8,12 +8,15 @@ from .cci import cci
from .cg import cg
from .cmo import cmo
from .coppock import coppock
from .er import er
from .eri import eri
from .fisher import fisher
from .inertia import inertia
from .kdj import kdj
from .kst import kst
from .macd import macd
from .mom import mom
from .pgo import pgo
from .ppo import ppo
from .psl import psl
from .pvo import pvo
@@ -21,6 +24,7 @@ from .roc import roc
from .rsi import rsi
from .rvgi import rvgi
from .slope import slope
from .squeeze import squeeze
from .stoch import stoch
from .trix import trix
from .tsi import tsi
+94
View File
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, concat
from pandas_ta.overlap import rma
from pandas_ta.utils import get_drift, get_offset, verify_series, signals
def er(close, length=None, drift=None, offset=None, **kwargs):
"""Indicator: Efficiency Ratio (ER)"""
# Validate arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 10
offset = get_offset(offset)
drift = get_drift(drift)
# Calculate Result
abs_diff = close.diff(length).abs()
abs_volatility = close.diff(drift).abs()
er = abs_diff
er /= abs_volatility.rolling(window=length).sum()
# Offset
if offset != 0:
er = er.shift(offset)
# Handle fills
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
er.name = f"ER_{length}"
er.category = "momentum"
signal_indicators = kwargs.pop('signal_indicators', False)
if signal_indicators:
signalsdf = concat(
[
DataFrame(
{er.name: er}
),
signals(
indicator=er,
xa=kwargs.pop('xa', 80),
xb=kwargs.pop('xb', 20),
xserie=kwargs.pop('xserie', None),
xserie_a=kwargs.pop('xserie_a', None),
xserie_b=kwargs.pop('xserie_b', None),
cross_values=kwargs.pop('cross_values', False),
cross_series=kwargs.pop('cross_series', True),
offset=offset,
),
],
axis=1
)
return signalsdf
else:
return er
er.__doc__ = \
"""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.
It is calculated by dividing the net change in price movement over N periods by the sum of the absolute net changes over the same N periods.
Sources:
https://help.tc2000.com/m/69404/l/749623-kaufman-efficiency-ratio
Calculation:
Default Inputs:
length=10
ABS = Absolute Value
EMA = Exponential Moving Average
abs_diff = ABS(close.diff(length))
volatility = ABS(close.diff(1))
ER = abs_diff / SUM(volatility, length)
Args:
close (pd.Series): Series of 'close's
length (int): It's period. Default: 1
offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.Series: New feature generated.
"""
+85
View File
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame
from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series
def eri(high, low, close, length=None, offset=None, **kwargs):
"""Indicator: Elder Ray Index (ERI)"""
# Validate arguments
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
length = int(length) if length and length > 0 else 13
offset = get_offset(offset)
# Calculate Result
ema_ = ema(close, length)
bull = high - ema_
bear = low - ema_
# Offset
if offset != 0:
bull = bull.shift(offset)
bear = bear.shift(offset)
# Handle fills
if 'fillna' in kwargs:
bull.fillna(kwargs['fillna'], inplace=True)
bear.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
bull.fillna(method=kwargs['fill_method'], inplace=True)
bear.fillna(method=kwargs['fill_method'], inplace=True)
# Name and Categorize it
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}"
df.category = bull.category
return df
eri.__doc__ = \
"""Elder Ray Index (ERI)
Elder's Bulls Ray Index contains his Bull and Bear Powers. Which are useful ways
to look at the price and see the strength behind the market. Bull Power
measures the capability of buyers in the market, to lift prices above an average
consensus of value.
Bears Power measures the capability of sellers, to drag prices below an average
consensus of value. Using them in tandem with a measure of trend allows you to
identify favourable entry points. We hope you've found this to be a useful
discussion of the Bulls and Bears Power indicators.
Sources:
https://admiralmarkets.com/education/articles/forex-indicators/bears-and-bulls-power-indicator
Calculation:
Default Inputs:
length=13
EMA = Exponential Moving Average
BULLPOWER = high - EMA(close, length)
BEARPOWER = low - EMA(close, length)
Args:
high (pd.Series): Series of 'high's
low (pd.Series): Series of 'low's
close (pd.Series): Series of 'close's
length (int): It's period. Default: 14
offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.DataFrame: bull power and bear power columns.
"""
+69
View File
@@ -0,0 +1,69 @@
# -*- 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
def pgo(high, low, close, length=None, offset=None, **kwargs):
"""Indicator: Pretty Good Oscillator (PGO)"""
# Validate arguments
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
length = int(length) if length and length > 0 else 14
offset = get_offset(offset)
# Calculate Result
pgo = close - sma(close, length)
pgo /= ema(atr(high, low, close, length), length)
# Offset
if offset != 0:
pgo = pgo.shift(offset)
# Handle fills
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
pgo.name = f"PGO_{length}"
pgo.category = "momentum"
return pgo
pgo.__doc__ = \
"""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
use it as a breakout system for longer term trades. Long if greater than 3.0 and
short if less than -3.0.
Sources:
https://library.tradingtechnologies.com/trade/chrt-ti-pretty-good-oscillator.html
Calculation:
Default Inputs:
length=14
ATR = Average True Range
SMA = Simple Moving Average
EMA = Exponential Moving Average
PGO = (close - SMA(close, length)) / EMA(ATR(high, low, close, length), length)
Args:
high (pd.Series): Series of 'high's
low (pd.Series): Series of 'low's
close (pd.Series): Series of 'close's
length (int): It's period. Default: 14
offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.Series: New feature generated.
"""
+14 -16
View File
@@ -29,31 +29,29 @@ def rsi(close, length=None, scalar=None, drift=None, offset=None, **kwargs):
rsi = rsi.shift(offset)
# Handle fills
if 'fillna' in kwargs:
rsi.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
rsi.fillna(method=kwargs['fill_method'], inplace=True)
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
rsi.name = f"RSI_{length}"
rsi.category = 'momentum'
rsi.category = "momentum"
signal_indicators = kwargs.pop('signal_indicators', False)
signal_indicators = kwargs.pop("signal_indicators", False)
if signal_indicators:
signalsdf = concat(
[
DataFrame(
{rsi.name: rsi}
),
DataFrame({rsi.name: rsi}),
signals(
indicator=rsi,
xa=kwargs.pop('xa', 80),
xb=kwargs.pop('xb', 20),
xserie=kwargs.pop('xserie', None),
xserie_a=kwargs.pop('xserie_a', None),
xserie_b=kwargs.pop('xserie_b', None),
cross_values=kwargs.pop('cross_values', False),
cross_series=kwargs.pop('cross_series', True),
xa=kwargs.pop("xa", 80),
xb=kwargs.pop("xb", 20),
xserie=kwargs.pop("xserie", None),
xserie_a=kwargs.pop("xserie_a", None),
xserie_b=kwargs.pop("xserie_b", None),
cross_values=kwargs.pop("cross_values", False),
cross_series=kwargs.pop("cross_series", True),
offset=offset,
),
],
+222
View File
@@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
from numpy import NaN as npNaN
from pandas import concat, DataFrame
from pandas_ta.momentum import mom
from pandas_ta.overlap import ema, linreg, sma
from pandas_ta.statistics import stdev
from pandas_ta.trend import decreasing, increasing
from pandas_ta.volatility import bbands, kc, true_range
from pandas_ta.utils import get_drift, get_offset, high_low_range
from pandas_ta.utils import unsigned_differences, verify_series
def squeeze(high, low, close, bb_length=None, bb_std=None, kc_length=None, kc_scalar=None, mom_length=None, mom_smooth=None, use_tr=None, offset=None, **kwargs):
"""Indicator: Squeeze Momentum (SQZ)"""
# Validate arguments
high = verify_series(high)
low = verify_series(low)
close = verify_series(close)
offset = get_offset(offset)
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.
kc_length = int(kc_length) if kc_length and kc_length > 0 else 20
kc_scalar = float(kc_scalar) if kc_scalar and kc_scalar > 0 else 1.5
mom_length = int(mom_length) if mom_length and mom_length > 0 else 12
mom_smooth = int(mom_smooth) if mom_smooth and mom_smooth > 0 else 6
use_tr = kwargs.setdefault("tr", True)
asint = kwargs.pop("asint", True)
mamode = kwargs.pop("mamode", "sma").lower()
lazybear = kwargs.pop("lazybear", False)
detailed = kwargs.pop("detailed", False)
def simplify_columns(df, n=3):
df.columns = df.columns.str.lower()
return [c.split('_')[0][n-1:n] for c in df.columns]
# Calculate Result
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)
# Simplify KC and BBAND column names for dynamic access
bbd.columns = simplify_columns(bbd)
kch.columns = simplify_columns(kch)
if lazybear:
highest_high = high.rolling(kc_length).max()
lowest_low = low.rolling(kc_length).min()
avg_ = 0.25 * (highest_high + lowest_low) + 0.5 * kch.b
squeeze = linreg(close - avg_, length=kc_length)
else:
momo = mom(close, length=mom_length)
if mamode == "ema":
squeeze = ema(momo, length=mom_smooth)
else:
squeeze = sma(momo, length=mom_smooth)
# Classify Squeezes
squeeze_on = (bbd.l > kch.l) & (bbd.u < kch.u)
squeeze_off = (bbd.l < kch.l) & (bbd.u > kch.u)
no_squeeze = ~squeeze_on & ~squeeze_off
# Offset
if offset != 0:
squeeze = squeeze.shift(offset)
squeeze_on = squeeze_on.shift(offset)
squeeze_off = squeeze_off.shift(offset)
no_squeeze = no_squeeze.shift(offset)
# Handle fills
if "fillna" in kwargs:
squeeze.fillna(kwargs["fillna"], inplace=True)
squeeze_on.fillna(kwargs["fillna"], inplace=True)
squeeze_off.fillna(kwargs["fillna"], inplace=True)
no_squeeze.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
squeeze.fillna(method=kwargs["fill_method"], inplace=True)
squeeze_on.fillna(method=kwargs["fill_method"], inplace=True)
squeeze_off.fillna(method=kwargs["fill_method"], inplace=True)
no_squeeze.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
_props = "" if use_tr else "hlr"
_props += f"_{bb_length}_{bb_std}_{kc_length}_{kc_scalar}"
_props += "_LB" if lazybear else ""
squeeze.name = f"SQZ{_props}"
data = {
squeeze.name: squeeze,
f"SQZ_ON": squeeze_on.astype(int) if asint else squeeze_on,
f"SQZ_OFF": squeeze_off.astype(int) if asint else squeeze_off,
f"SQZ_NO": no_squeeze.astype(int) if asint else no_squeeze
}
df = DataFrame(data)
df.name = squeeze.name
df.category = squeeze.category = "momentum"
# Detailed Squeeze Series
if detailed:
pos_squeeze = squeeze[squeeze >= 0]
neg_squeeze = squeeze[squeeze < 0]
pos_inc, pos_dec = unsigned_differences(pos_squeeze, asint=True)
neg_inc, neg_dec = unsigned_differences(neg_squeeze, asint=True)
pos_inc *= squeeze
pos_dec *= squeeze
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)
sqz_inc = squeeze * increasing(squeeze)
sqz_dec = squeeze * decreasing(squeeze)
sqz_inc.replace(0, npNaN, inplace=True)
sqz_dec.replace(0, npNaN, inplace=True)
# Handle fills
if "fillna" in kwargs:
sqz_inc.fillna(kwargs["fillna"], inplace=True)
sqz_dec.fillna(kwargs["fillna"], inplace=True)
pos_inc.fillna(kwargs["fillna"], inplace=True)
pos_dec.fillna(kwargs["fillna"], inplace=True)
neg_dec.fillna(kwargs["fillna"], inplace=True)
neg_inc.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
sqz_inc.fillna(method=kwargs["fill_method"], inplace=True)
sqz_dec.fillna(method=kwargs["fill_method"], inplace=True)
pos_inc.fillna(method=kwargs["fill_method"], inplace=True)
pos_dec.fillna(method=kwargs["fill_method"], inplace=True)
neg_dec.fillna(method=kwargs["fill_method"], inplace=True)
neg_inc.fillna(method=kwargs["fill_method"], inplace=True)
df[f"SQZ_INC"] = sqz_inc
df[f"SQZ_DEC"] = sqz_dec
df[f"SQZ_PINC"] = pos_inc
df[f"SQZ_PDEC"] = pos_dec
df[f"SQZ_NDEC"] = neg_dec
df[f"SQZ_NINC"] = neg_inc
return df
squeeze.__doc__ = \
"""Squeeze (SQZ)
The default is based on John Carter's "TTM Squeeze" indicator, as discussed
in his book "Mastering the Trade" (chapter 11). The Squeeze indicator attempts
to capture the relationship between two studies: Bollinger Bands® and Keltner's
Channels. When the volatility increases, so does the distance between the bands,
conversely, when the volatility declines, the distance also decreases. It finds
sections of the Bollinger Bands® study which fall inside the Keltner's Channels.
Sources:
https://tradestation.tradingappstore.com/products/TTMSqueeze
https://www.tradingview.com/scripts/lazybear/
https://tlc.thinkorswim.com/center/reference/Tech-Indicators/studies-library/T-U/TTM-Squeeze
Calculation:
Default Inputs:
bb_length=20, bb_std=2, kc_length=20, kc_scalar=1.5, mom_length=12,
mom_smooth=12, tr=True, lazybear=False,
BB = Bollinger Bands
KC = Keltner Channels
MOM = Momentum
SMA = Simple Moving Average
EMA = Exponential Moving Average
TR = True Range
RANGE = TR(high, low, close) if using_tr else high - low
BB_LOW, BB_MID, BB_HIGH = BB(close, bb_length, std=bb_std)
KC_LOW, KC_MID, KC_HIGH = KC(high, low, close, kc_length, kc_scalar, TR)
if lazybear:
HH = high.rolling(kc_length).max()
LL = low.rolling(kc_length).min()
AVG = 0.25 * (HH + LL) + 0.5 * KC_MID
SQZ = linreg(close - AVG, kc_length)
else:
MOMO = MOM(close, mom_length)
if mamode == "ema":
SQZ = EMA(MOMO, mom_smooth)
else:
SQZ = EMA(momo, mom_smooth)
SQZ_ON = (BB_LOW > KC_LOW) and (BB_HIGH < KC_HIGH)
SQZ_OFF = (BB_LOW < KC_LOW) and (BB_HIGH > KC_HIGH)
NO_SQZ = !SQZ_ON and !SQZ_OFF
Args:
high (pd.Series): Series of 'high's
low (pd.Series): Series of 'low's
close (pd.Series): Series of 'close's
bb_length (int): Bollinger Bands period. Default: 20
bb_std (float): Bollinger Bands Std. Dev. Default: 2
kc_length (int): Keltner Channel period. Default: 20
kc_scalar (float): Keltner Channel scalar. Default: 1.5
mom_length (int): Momentum Period. Default: 12
mom_smooth (int): Smoothing Period of Momentum. Default: 6
mamode (str): Only "ema" or "sma". Default: "sma"
offset (int): How many periods to offset the result. Default: 0
Kwargs:
tr (value, optional): Use True Range for Keltner Channels. Default: True
asint (value, optional): Use integers instead of bool. Default: True
mamode (value, optional): Which MA to use. Default: "sma"
lazybear (value, optional): Use LazyBear's TradingView implementation.
Default: False
detailed (value, optional): Return additional variations of SQZ for
visualization. Default: False
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
Returns:
pd.DataFrame: SQZ, SQZ_ON, SQZ_OFF, NO_SQZ columns by default. More
detailed columns if 'detailed' kwarg is True.
"""
+10 -10
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from ..utils import get_drift, get_offset, verify_series
from pandas_ta.utils import get_drift, get_offset, verify_series
def willr(high, low, close, length=None, offset=None, **kwargs):
"""Indicator: William's Percent R (WILLR)"""
@@ -8,7 +8,7 @@ def willr(high, low, close, length=None, offset=None, **kwargs):
low = verify_series(low)
close = verify_series(close)
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
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
offset = get_offset(offset)
# Calculate Result
@@ -22,14 +22,14 @@ def willr(high, low, close, length=None, offset=None, **kwargs):
willr = willr.shift(offset)
# Handle fills
if 'fillna' in kwargs:
willr.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
willr.fillna(method=kwargs['fill_method'], inplace=True)
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
willr.name = f"WILLR_{length}"
willr.category = 'momentum'
willr.category = "momentum"
return willr
@@ -47,10 +47,10 @@ Sources:
Calculation:
Default Inputs:
length=20
lowest_low = low.rolling(length).min()
highest_high = high.rolling(length).max()
LL = low.rolling(length).min()
HH = high.rolling(length).max()
WILLR = 100 * ((close - lowest_low) / (highest_high - lowest_low) - 1)
WILLR = 100 * ((close - LL) / (HH - LL) - 1)
Args:
high (pd.Series): Series of 'high's
+6 -6
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from numpy import log as nplog
from ..utils import get_offset, verify_series
from pandas_ta.utils import get_offset, verify_series
def log_return(close, length=None, cumulative=False, offset=None, **kwargs):
"""Indicator: Log Return"""
@@ -20,14 +20,14 @@ def log_return(close, length=None, cumulative=False, offset=None, **kwargs):
log_return = log_return.shift(offset)
# Handle fills
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)
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
log_return.name = f"{'CUM' if cumulative else ''}LOGRET_{length}"
log_return.category = 'performance'
log_return.category = "performance"
return log_return
+2 -2
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from ..utils import get_offset, verify_series
from pandas_ta.utils import get_offset, verify_series
def percent_return(close, length=None, cumulative=False, offset=None, **kwargs):
"""Indicator: Percent Return"""
@@ -20,7 +20,7 @@ def percent_return(close, length=None, cumulative=False, offset=None, **kwargs):
# Name & Category
pct_return.name = f"{'CUM' if cumulative else ''}PCTRET_{length}"
pct_return.category = 'performance'
pct_return.category = "performance"
return pct_return
+10 -6
View File
@@ -2,23 +2,27 @@
from pandas import Series
from .log_return import log_return
from .percent_return import percent_return
from ..utils import get_offset, verify_series, zero
from pandas_ta.utils import get_offset, verify_series, zero
def trend_return(close, trend, log=True, cumulative=None, offset=None, trend_reset=0, **kwargs):
def trend_return(close, trend, log=True, cumulative=None, trend_reset=0, offset=None, **kwargs):
"""Indicator: Trend Return"""
# Validate Arguments
close = verify_series(close)
trend = verify_series(trend)
offset = get_offset(offset)
cumulative = cumulative if cumulative is not None and isinstance(cumulative, bool) else False
trend_reset = int(trend_reset) if trend_reset and isinstance(trend_reset, int) else 0
offset = get_offset(offset)
# Calculate Result
returns = log_return(close, cumulative=False) if log else percent_return(close, cumulative=False)
tsum = 0
m = trend.size
if log:
returns = log_return(close, cumulative=False)
else:
returns = percent_return(close, cumulative=False)
trend = trend.astype(int)
returns = (trend * returns).apply(zero)
tsum = 0
m = trend.size
result = []
for i in range(0, m):
if trend[i] == trend_reset:
+6 -6
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from ..utils import get_offset, verify_series
from pandas_ta.utils import get_offset, verify_series
def increasing(close, length=None, asint=True, offset=None, **kwargs):
"""Indicator: Increasing"""
@@ -18,14 +18,14 @@ def increasing(close, length=None, asint=True, offset=None, **kwargs):
increasing = increasing.shift(offset)
# Handle fills
if 'fillna' in kwargs:
increasing.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
increasing.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
increasing.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
increasing.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
increasing.name = f"INC_{length}"
increasing.category = 'trend'
increasing.category = "trend"
return increasing
+26 -37
View File
@@ -45,7 +45,7 @@ def _above_below(
# Name & Category
current.name = f"{series_a.name}_{'A' if above else 'B'}_{series_b.name}"
current.category = 'utility'
current.category = "utility"
return current
@@ -70,7 +70,7 @@ def above_value(
if not isinstance(value, (int, float, complex)):
print("[X] value is not a number")
return
series_b = pd.Series(value, index=series_a.index, name=f"{value}".replace('.','_'))
series_b = pd.Series(value, index=series_a.index, name=f"{value}".replace(".","_"))
return _above_below(series_a, series_b, above=True, asint=asint, offset=offset, **kwargs)
@@ -94,7 +94,7 @@ def below_value(
if not isinstance(value, (int, float, complex)):
print("[X] value is not a number")
return
series_b = pd.Series(value, index=series_a.index, name=f"{value}".replace('.','_'))
series_b = pd.Series(value, index=series_a.index, name=f"{value}".replace(".","_"))
return _above_below(series_a, series_b, above=False, asint=asint, offset=offset, **kwargs)
@@ -106,10 +106,10 @@ def category_files(category: str) -> list:
def combination(**kwargs):
"""https://stackoverflow.com/questions/4941753/is-there-a-math-ncr-function-in-python"""
n = int(math.fabs(kwargs.pop('n', 1)))
r = int(math.fabs(kwargs.pop('r', 0)))
n = int(math.fabs(kwargs.pop("n", 1)))
r = int(math.fabs(kwargs.pop("r", 0)))
if kwargs.pop('repetition', False) or kwargs.pop('multichoose', False):
if kwargs.pop("repetition", False) or kwargs.pop("multichoose", False):
n = n + r - 1
# if r < 0: return None
@@ -130,7 +130,7 @@ def cross_value(
offset: int = None,
**kwargs
):
series_b = pd.Series(value, index=series_a.index, name=f"{value}".replace('.','_'))
series_b = pd.Series(value, index=series_a.index, name=f"{value}".replace(".","_"))
return cross(series_a, series_b, above, asint, offset, **kwargs)
@@ -164,7 +164,7 @@ def cross(
# Name & Category
cross.name = f"{series_a.name}_{'XA' if above else 'XB'}_{series_b.name}"
cross.category = 'utility'
cross.category = "utility"
return cross
@@ -228,37 +228,26 @@ def signals(indicator, xa, xb, cross_values, xserie, xserie_a, xserie_b, cross_s
def df_error_analysis(dfA: pd.DataFrame, dfB: pd.DataFrame, **kwargs) -> pd.DataFrame:
""" """
col = kwargs.pop('col', None)
corr_method = kwargs.pop('corr_method', 'pearson')
col = kwargs.pop("col", None)
corr_method = kwargs.pop("corr_method", "pearson")
# Find their differences
# Find their differences and correlation
diff = dfA - dfB
df = pd.DataFrame({'diff': diff.describe()})
extra = pd.DataFrame(
[diff.var(), diff.mad(), diff.sem(), dfA.corr(dfB, method=corr_method)],
index=['var', 'mad', 'sem', 'corr']
)
# Append the differences to the DataFrame
df = df['diff'].append(extra, ignore_index=False)[0]
corr = dfA.corr(dfB, method=corr_method)
# For plotting
if kwargs.pop('plot', False):
if kwargs.pop("plot", False):
diff.hist()
if diff[diff > 0].any():
diff.plot(kind='kde')
if col is not None:
return df[col]
else:
return df
diff.plot(kind="kde")
return corr
def fibonacci(**kwargs) -> np.ndarray:
"""Fibonacci Sequence as a numpy array"""
n = int(math.fabs(kwargs.pop('n', 2)))
zero = kwargs.pop('zero', False)
weighted = kwargs.pop('weighted', False)
n = int(math.fabs(kwargs.pop("n", 2)))
zero = kwargs.pop("zero", False)
weighted = kwargs.pop("weighted", False)
if zero:
a, b = 0, 1
@@ -322,8 +311,8 @@ def pascals_triangle(n: int = None, **kwargs) -> np.ndarray:
=> inverse weighted: [0.9375, 0.75, 0.625, 0.75, 0.9375]
"""
n = int(math.fabs(n)) if n is not None else 0
weighted = kwargs.pop('weighted', False)
inverse = kwargs.pop('inverse', False)
weighted = kwargs.pop("weighted", False)
inverse = kwargs.pop("inverse", False)
# Calculation
triangle = np.array([combination(n=n, r=i) for i in range(0, n + 1)])
@@ -373,7 +362,7 @@ def symmetric_triangle(n: int = None, **kwargs) -> list:
=> weighted: [0.16666667 0.33333333 0.33333333 0.16666667]
"""
n = int(math.fabs(n)) if n is not None else 2
weighted = kwargs.pop('weighted', False)
weighted = kwargs.pop("weighted", False)
if n == 2:
triangle = [1, 1]
@@ -418,7 +407,7 @@ def unsigned_differences(series: pd.Series, amount: int = None, **kwargs) -> pd.
negative[negative >= 0] = 0
negative[negative < 0] = 1
if kwargs.pop('asint', False):
if kwargs.pop("asint", False):
positive = positive.astype(int)
negative = negative.astype(int)
@@ -440,7 +429,7 @@ def weights(w):
def zero(x: [int, float]) -> [int, float]:
"""If the value is close to zero, then return zero.
Otherwise return the value."""
return 0 if -sflt.epsilon < x and x < sflt.epsilon else x
return 0 if abs(x) < sflt.epsilon else x
# Candle Functions
@@ -450,8 +439,8 @@ def candle_color(open_, close):
color[close < open_] = -1
return color
def real_body(open_, close):
return non_zero_range(open_, close)
def real_body(close, open_):
return non_zero_range(close, open_)
def high_low_range(high, low):
return non_zero_range(high, low)
return non_zero_range(high, low)
+2 -1
View File
@@ -9,4 +9,5 @@ from .massi import massi
from .pdist import pdist
from .natr import natr
from .rvi import rvi
from .true_range import true_range
from .true_range import true_range
from .ui import ui
+19 -20
View File
@@ -1,27 +1,26 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame
from ..overlap.ema import ema
from ..overlap.sma import sma
from ..statistics.stdev import stdev
from ..utils import get_offset, verify_series
from pandas_ta.overlap import ema, sma
from pandas_ta.statistics import stdev
from pandas_ta.utils import get_offset, verify_series
def bbands(close, length=None, std=None, mamode=None, offset=None, **kwargs):
"""Indicator: Bollinger Bands (BBANDS)"""
# Validate arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 5
min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
std = float(std) if std and std > 0 else 2.
mamode = mamode.lower() if mamode else 'sma'
mamode = mamode.lower() if mamode else "sma"
offset = get_offset(offset)
# Calculate Result
standard_deviation = stdev(close=close, length=length)
deviations = std * standard_deviation
if mamode is None or mamode == 'sma':
if mamode is None or mamode == "sma":
mid = sma(close=close, length=length)
elif mamode == 'ema':
elif mamode == "ema":
mid = ema(close=close, length=length, **kwargs)
lower = mid - deviations
@@ -34,26 +33,26 @@ def bbands(close, length=None, std=None, mamode=None, offset=None, **kwargs):
upper = upper.shift(offset)
# Handle fills
if 'fillna' in kwargs:
lower.fillna(kwargs['fillna'], inplace=True)
mid.fillna(kwargs['fillna'], inplace=True)
upper.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
lower.fillna(method=kwargs['fill_method'], inplace=True)
mid.fillna(method=kwargs['fill_method'], inplace=True)
upper.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
lower.fillna(kwargs["fillna"], inplace=True)
mid.fillna(kwargs["fillna"], inplace=True)
upper.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
lower.fillna(method=kwargs["fill_method"], inplace=True)
mid.fillna(method=kwargs["fill_method"], inplace=True)
upper.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
lower.name = f"BBL_{length}_{std}"
mid.name = f"BBM_{length}_{std}"
upper.name = f"BBU_{length}_{std}"
mid.category = upper.category = lower.category = 'volatility'
mid.category = upper.category = lower.category = "volatility"
# Prepare DataFrame to return
data = {lower.name: lower, mid.name: mid, upper.name: upper}
bbandsdf = DataFrame(data)
bbandsdf.name = f"BBANDS_{length}_{std}"
bbandsdf.category = 'volatility'
bbandsdf.category = "volatility"
return bbandsdf
@@ -74,7 +73,7 @@ Calculation:
SMA = Simple Moving Average
STDEV = Standard Deviation
stdev = STDEV(close, length)
if 'ema':
if "ema":
MID = EMA(close, length)
else:
MID = SMA(close, length)
@@ -86,7 +85,7 @@ Args:
close (pd.Series): Series of 'close's
length (int): The short period. Default: 20
std (int): The long period. Default: 2
mamode (str): Two options: None or 'ema'. Default: 'ema'
mamode (str): Two options: None or "ema". Default: "ema"
offset (int): How many periods to offset the result. Default: 0
Kwargs:
+40 -32
View File
@@ -2,9 +2,9 @@
from numpy import sqrt as npsqrt
from pandas import DataFrame
from .atr import atr
from ..overlap.hlc3 import hlc3
from ..statistics.variance import variance
from ..utils import get_offset, non_zero_range, verify_series
from .true_range import true_range
from pandas_ta.overlap import ema, hlc3, sma
from pandas_ta.utils import get_offset, high_low_range, non_zero_range, verify_series
def kc(high, low, close, length=None, scalar=None, mamode=None, offset=None, **kwargs):
@@ -14,22 +14,26 @@ def kc(high, low, close, length=None, scalar=None, mamode=None, offset=None, **k
low = verify_series(low)
close = verify_series(close)
length = int(length) if length and length > 0 else 20
min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length
min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else length
scalar = float(scalar) if scalar and scalar > 0 else 2
use_tr = kwargs.pop("tr", True)
mamode = mamode.lower() if mamode else None
offset = get_offset(offset)
# Calculate Result
std = variance(close=close, length=length).apply(npsqrt)
if mamode == 'ema':
basis = close.ewm(span=length, min_periods=min_periods).mean()
band = atr(high=high, low=low, close=close)
if use_tr:
range_ = true_range(high, low, close)
else:
hl_range = non_zero_range(high, low)
typical_price = hlc3(high=high, low=low, close=close)
basis = typical_price.rolling(length, min_periods=min_periods).mean()
band = hl_range.rolling(length, min_periods=min_periods).mean()
range_ = high_low_range(high, low)
_mode = ""
if mamode == "sma":
basis = sma(close, length)
band = sma(range_, length=length)
_mode += "s"
elif mamode is None or mamode == "ema":
basis = ema(close, length=length)
band = ema(range_, length=length)
lower = basis - scalar * band
upper = basis + scalar * band
@@ -41,26 +45,27 @@ def kc(high, low, close, length=None, scalar=None, mamode=None, offset=None, **k
upper = upper.shift(offset)
# Handle fills
if 'fillna' in kwargs:
lower.fillna(kwargs['fillna'], inplace=True)
basis.fillna(kwargs['fillna'], inplace=True)
upper.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
lower.fillna(method=kwargs['fill_method'], inplace=True)
basis.fillna(method=kwargs['fill_method'], inplace=True)
upper.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
lower.fillna(kwargs["fillna"], inplace=True)
basis.fillna(kwargs["fillna"], inplace=True)
upper.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
lower.fillna(method=kwargs["fill_method"], inplace=True)
basis.fillna(method=kwargs["fill_method"], inplace=True)
upper.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
lower.name = f"KCL_{length}"
basis.name = f"KCB_{length}"
upper.name = f"KCU_{length}"
basis.category = upper.category = lower.category = 'volatility'
_props = f"{_mode if len(_mode) else ''}_{length}_{scalar}"
lower.name = f"KCL{_props}"
basis.name = f"KCB{_props}"
upper.name = f"KCU{_props}"
basis.category = upper.category = lower.category = "volatility"
# Prepare DataFrame to return
data = {lower.name: lower, basis.name: basis, upper.name: upper}
kcdf = DataFrame(data)
kcdf.name = f"KC_{length}"
kcdf.category = 'volatility'
kcdf.name = f"KC{_props}"
kcdf.category = basis.category
return kcdf
@@ -77,14 +82,17 @@ Sources:
Calculation:
Default Inputs:
length=20, scalar=2
length=20, scalar=2, mamode=None
ATR = Average True Range
EMA = Exponential Moving Average
SMA = Simple Moving Average
if 'ema':
BAND = ATR(high, low, close)
if mamode == "ema":
BASIS = EMA(close, length)
BAND = ATR(high, low, close)
else:
elif mamode == "sma":
BASIS = SMA(close, length)
else: # Typical Price
hl_range = high - low
tp = typical_price = hlc3(high, low, close)
BASIS = SMA(tp, length)
@@ -99,7 +107,7 @@ Args:
close (pd.Series): Series of 'close's
length (int): The short period. Default: 20
scalar (float): A positive float to scale the bands. Default: 2
mamode (str): Two options: None or 'ema'. Default: 'ema'
mamode (str): Two options: None or "ema". Default: "ema"
offset (int): How many periods to offset the result. Default: 0
Kwargs:
+85
View File
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
from numpy import sqrt as npsqrt
from pandas_ta.overlap import sma
from ..utils import get_offset, non_zero_range, verify_series
def ui(close, length=None, scalar=None, offset=None, **kwargs):
"""Indicator: Ulcer Index (UI)"""
# Validate arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 14
scalar = float(scalar) if scalar and scalar > 0 else 100
offset = get_offset(offset)
# Calculate Result
highest_close = close.rolling(length).max()
downside = scalar * (close - highest_close)
downside /= highest_close
d2 = downside * downside
everget = kwargs.pop("everget", False)
if everget:
# Everget uses SMA instead of SUM for calculation
ui = (sma(d2, length) / length).apply(npsqrt)
else:
ui = (d2.rolling(length).sum() / length).apply(npsqrt)
# Offset
if offset != 0:
ui = ui.shift(offset)
# Handle fills
if 'fillna' in kwargs:
ui.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
ui.fillna(method=kwargs['fill_method'], inplace=True)
# Name and Categorize it
ui.name = f"UI{'' if not everget else 'e'}_{length}"
ui.category = "volatility"
return ui
ui.__doc__ = \
"""Ulcer Index (UI)
The Ulcer Index by Peter Martin measures the downside volatility with the use of
the Quadratic Mean, which has the effect of emphasising large drawdowns.
Sources:
https://library.tradingtechnologies.com/trade/chrt-ti-ulcer-index.html
https://en.wikipedia.org/wiki/Ulcer_index
http://www.tangotools.com/ui/ui.htm
Calculation:
Default Inputs:
length=14, scalar=100
HC = Highest Close
SMA = Simple Moving Average
HCN = HC(close, length)
DOWNSIDE = scalar * (close - HCN) / HCN
if kwargs["everget"]:
UI = SQRT(SMA(DOWNSIDE^2, length) / length)
else:
UI = SQRT(SUM(DOWNSIDE^2, length) / length)
Args:
high (pd.Series): Series of 'high's
close (pd.Series): Series of 'close's
length (int): The short period. Default: 14
scalar (float): A positive float to scale the bands. Default: 100
offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
fill_method (value, optional): Type of fill method
everget (value, optional): TradingView's Evergets SMA instead of SUM
calculation. Default: False
Returns:
pd.Series: New feature
"""
+25 -30
View File
@@ -1,14 +1,9 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame
from .obv import obv
from ..overlap.ema import ema
from ..overlap.hma import hma
from ..overlap.linreg import linreg
from ..overlap.sma import sma
from ..overlap.wma import wma
from ..trend.long_run import long_run
from ..trend.short_run import short_run
from ..utils import get_offset, verify_series
from pandas_ta.overlap import ema, hma, linreg, sma, wma
from pandas_ta.trend import long_run, short_run
from pandas_ta.utils import get_offset, verify_series
def aobv(close, volume, fast=None, slow=None, mamode=None, max_lookback=None, min_lookback=None, offset=None, **kwargs):
"""Indicator: Archer On Balance Volume (AOBV)"""
@@ -16,31 +11,31 @@ def aobv(close, volume, fast=None, slow=None, mamode=None, max_lookback=None, mi
close = verify_series(close)
volume = verify_series(volume)
offset = get_offset(offset)
fast = int(fast) if fast and fast > 0 else 2
slow = int(slow) if slow and slow > 0 else 4
fast = int(fast) if fast and fast > 0 else 4
slow = int(slow) if slow and slow > 0 else 12
max_lookback = int(max_lookback) if max_lookback and max_lookback > 0 else 2
min_lookback = int(min_lookback) if min_lookback and min_lookback > 0 else 2
if slow < fast:
fast, slow = slow, fast
mamode = mamode.upper() if mamode else None
run_length = kwargs.pop('run_length', 2)
run_length = kwargs.pop("run_length", 2)
# Calculate Result
obv_ = obv(close=close, volume=volume, **kwargs)
if mamode is None or mamode == 'EMA':
mamode = 'EMA'
if mamode is None or mamode == "EMA":
mamode = "EMA"
maf = ema(close=obv_, length=fast, **kwargs)
mas = ema(close=obv_, length=slow, **kwargs)
elif mamode == 'HMA':
elif mamode == "HMA":
maf = hma(close=obv_, length=fast, **kwargs)
mas = hma(close=obv_, length=slow, **kwargs)
elif mamode == 'LINREG':
elif mamode == "LINREG":
maf = linreg(close=obv_, length=fast, **kwargs)
mas = linreg(close=obv_, length=slow, **kwargs)
elif mamode == 'SMA':
elif mamode == "SMA":
maf = sma(close=obv_, length=fast, **kwargs)
mas = sma(close=obv_, length=slow, **kwargs)
elif mamode == 'WMA':
elif mamode == "WMA":
maf = wma(close=obv_, length=fast, **kwargs)
mas = wma(close=obv_, length=slow, **kwargs)
@@ -57,18 +52,18 @@ def aobv(close, volume, fast=None, slow=None, mamode=None, max_lookback=None, mi
obv_short = obv_short.shift(offset)
# # Handle fills
if 'fillna' in kwargs:
obv_.fillna(kwargs['fillna'], inplace=True)
maf.fillna(kwargs['fillna'], inplace=True)
mas.fillna(kwargs['fillna'], inplace=True)
obv_long.fillna(kwargs['fillna'], inplace=True)
obv_short.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
obv_.fillna(method=kwargs['fill_method'], inplace=True)
maf.fillna(method=kwargs['fill_method'], inplace=True)
mas.fillna(method=kwargs['fill_method'], inplace=True)
obv_long.fillna(method=kwargs['fill_method'], inplace=True)
obv_short.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
obv_.fillna(kwargs["fillna"], inplace=True)
maf.fillna(kwargs["fillna"], inplace=True)
mas.fillna(kwargs["fillna"], inplace=True)
obv_long.fillna(kwargs["fillna"], inplace=True)
obv_short.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
obv_.fillna(method=kwargs["fill_method"], inplace=True)
maf.fillna(method=kwargs["fill_method"], inplace=True)
mas.fillna(method=kwargs["fill_method"], inplace=True)
obv_long.fillna(method=kwargs["fill_method"], inplace=True)
obv_short.fillna(method=kwargs["fill_method"], inplace=True)
# Prepare DataFrame to return
data = {
@@ -84,6 +79,6 @@ def aobv(close, volume, fast=None, slow=None, mamode=None, max_lookback=None, mi
# Name and Categorize it
aobvdf.name = f"AOBV_{mamode}_{fast}_{slow}_{min_lookback}_{max_lookback}_{run_length}"
aobvdf.category = 'volume'
aobvdf.category = "volume"
return aobvdf
+11 -11
View File
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from ..utils import get_drift, get_offset, verify_series
from pandas_ta.overlap import ema, sma
from pandas_ta.utils import get_drift, get_offset, verify_series
def efi(close, volume, length=None, drift=None, mamode=None, offset=None, **kwargs):
"""Indicator: Elder's Force Index (EFI)"""
@@ -7,7 +8,6 @@ def efi(close, volume, length=None, drift=None, mamode=None, offset=None, **kwar
close = verify_series(close)
volume = verify_series(volume)
length = int(length) if length and length > 0 else 13
min_periods = int(kwargs['min_periods']) if 'min_periods' in kwargs and kwargs['min_periods'] is not None else length
drift = get_drift(drift)
mamode = mamode.lower() if mamode else None
offset = get_offset(offset)
@@ -15,24 +15,24 @@ def efi(close, volume, length=None, drift=None, mamode=None, offset=None, **kwar
# Calculate Result
pv_diff = close.diff(drift) * volume
if mamode == 'sma':
efi = pv_diff.rolling(length, min_periods=min_periods).mean()
if mamode == "sma":
efi = sma(pv_diff, length)
else:
efi = pv_diff.ewm(span=length, min_periods=min_periods).mean()
efi = ema(pv_diff, length)
# Offset
if offset != 0:
efi = efi.shift(offset)
# Handle fills
if 'fillna' in kwargs:
efi.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
efi.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
efi.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
efi.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
efi.name = f"EFI_{length}"
efi.category = 'volume'
efi.category = "volume"
return efi
@@ -65,7 +65,7 @@ Args:
volume (pd.Series): Series of 'volume's
length (int): The short period. Default: 13
drift (int): The diff period. Default: 1
mamode (str): Two options: None or 'sma'. Default: None
mamode (str): Two options: None or "sma". Default: None
offset (int): How many periods to offset the result. Default: 0
Kwargs:
+9 -10
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from ..overlap.hl2 import hl2
from ..utils import get_drift, get_offset, non_zero_range, verify_series
from pandas_ta.overlap import hl2, sma
from pandas_ta.utils import get_drift, get_offset, non_zero_range, verify_series
def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset=None, **kwargs):
"""Indicator: Ease of Movement (EOM)"""
@@ -9,33 +9,32 @@ def eom(high, low, close, volume, length=None, divisor=None, drift=None, offset=
low = verify_series(low)
close = verify_series(close)
volume = verify_series(volume)
high_low_range = non_zero_range(high, low)
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
divisor = divisor if divisor and divisor > 0 else 100000000
drift = get_drift(drift)
offset = get_offset(offset)
# Calculate Result
high_low_range = non_zero_range(high, low)
distance = hl2(high=high, low=low) - hl2(high=high.shift(drift), low=low.shift(drift))
box_ratio = volume / divisor
box_ratio /= high_low_range
eom = distance / box_ratio
eom = eom.rolling(length, min_periods=min_periods).mean()
eom = sma(eom, length=length)
# Offset
if offset != 0:
eom = eom.shift(offset)
# Handle fills
if 'fillna' in kwargs:
eom.fillna(kwargs['fillna'], inplace=True)
if 'fill_method' in kwargs:
eom.fillna(method=kwargs['fill_method'], inplace=True)
if "fillna" in kwargs:
eom.fillna(kwargs["fillna"], inplace=True)
if "fill_method" in kwargs:
eom.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
eom.name = f"EOM_{length}_{divisor}"
eom.category = 'volume'
eom.category = "volume"
return eom
+32
View File
@@ -144,6 +144,16 @@ class TestMomentum(TestCase):
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "COPC_11_14_10")
def test_er(self):
result = pandas_ta.er(self.close)
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "ER_10")
def test_eri(self):
result = pandas_ta.eri(self.high, self.low, self.close)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "ERI_13")
def test_fisher(self):
result = pandas_ta.fisher(self.high, self.low)
self.assertIsInstance(result, Series)
@@ -216,6 +226,11 @@ class TestMomentum(TestCase):
except Exception as ex:
error_analysis(result, CORRELATION, ex)
def test_pgo(self):
result = pandas_ta.pgo(self.high, self.low, self.close)
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "PGO_14")
def test_ppo(self):
result = pandas_ta.ppo(self.close)
self.assertIsInstance(result, DataFrame)
@@ -291,6 +306,23 @@ class TestMomentum(TestCase):
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "ANGLEd_1")
def test_squeeze(self):
result = pandas_ta.squeeze(self.high, self.low, self.close)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZ_20_2.0_20_1.5")
result = pandas_ta.squeeze(self.high, self.low, self.close, tr=False)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZhlr_20_2.0_20_1.5")
result = pandas_ta.squeeze(self.high, self.low, self.close, lazybear=True)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZ_20_2.0_20_1.5_LB")
result = pandas_ta.squeeze(self.high, self.low, self.close, tr=False, lazybear=True)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZhlr_20_2.0_20_1.5_LB")
def test_stoch(self):
result = pandas_ta.stoch(self.high, self.low, self.close, fast_k=14, slow_k=14, slow_d=14)
self.assertIsInstance(result, DataFrame)
+24
View File
@@ -68,6 +68,16 @@ class TestMomentumExtension(TestCase):
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "COPC_11_14_10")
def test_er_ext(self):
self.data.ta.er(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "ER_10")
def test_eri_ext(self):
self.data.ta.eri(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-2:]), ["BULLP_13", "BEARP_13"])
def test_fisher_ext(self):
self.data.ta.fisher(append=True)
self.assertIsInstance(self.data, DataFrame)
@@ -108,6 +118,11 @@ class TestMomentumExtension(TestCase):
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "MOM_10")
def test_pgo_ext(self):
self.data.ta.pgo(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "PGO_14")
def test_ppo_ext(self):
self.data.ta.ppo(append=True)
self.assertIsInstance(self.data, DataFrame)
@@ -151,6 +166,15 @@ class TestMomentumExtension(TestCase):
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "ANGLEd_1")
def test_squeeze_ext(self):
self.data.ta.squeeze(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-4:]), ["SQZ_20_2.0_20_1.5", "SQZ_ON", "SQZ_OFF", "SQZ_NO"])
self.data.ta.squeeze(tr=False, append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-4:]), ["SQZ_ON", "SQZ_OFF", "SQZ_NO", "SQZhlr_20_2.0_20_1.5"])
def test_stoch_ext(self):
self.data.ta.stoch(append=True)
self.assertIsInstance(self.data, DataFrame)
+15 -2
View File
@@ -100,7 +100,11 @@ class TestVolatility(TestCase):
def test_kc(self):
result = pandas_ta.kc(self.high, self.low, self.close)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "KC_20")
self.assertEqual(result.name, "KC_20_2")
result = pandas_ta.kc(self.high, self.low, self.close, mamode="sma")
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "KCs_20_2")
def test_massi(self):
result = pandas_ta.massi(self.high, self.low)
@@ -153,4 +157,13 @@ class TestVolatility(TestCase):
corr = pandas_ta.utils.df_error_analysis(result, expected, col=CORRELATION)
self.assertGreater(corr, CORRELATION_THRESHOLD)
except Exception as ex:
error_analysis(result, CORRELATION, ex)
error_analysis(result, CORRELATION, ex)
def test_ui(self):
result = pandas_ta.ui(self.close)
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "UI_14")
result = pandas_ta.ui(self.close, everget=True)
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "UIe_14")
+11 -2
View File
@@ -48,7 +48,7 @@ class TestVolatilityExtension(TestCase):
def test_kc_ext(self):
self.data.ta.kc(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-3:]), ["KCL_20", "KCB_20", "KCU_20"])
self.assertEqual(list(self.data.columns[-3:]), ["KCL_20_2", "KCB_20_2", "KCU_20_2"])
def test_massi_ext(self):
self.data.ta.massi(append=True)
@@ -83,4 +83,13 @@ class TestVolatilityExtension(TestCase):
def test_true_range_ext(self):
self.data.ta.true_range(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "TRUERANGE_1")
self.assertEqual(self.data.columns[-1], "TRUERANGE_1")
def test_ui_ext(self):
self.data.ta.ui(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "UI_14")
self.data.ta.ui(append=True, everget=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "UIe_14")
+1 -1
View File
@@ -72,7 +72,7 @@ class TestVolume(TestCase):
def test_aobv(self):
result = pandas_ta.aobv(self.close, self.volume_)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "AOBV_EMA_2_4_2_2_2")
self.assertEqual(result.name, "AOBV_EMA_4_12_2_2_2")
def test_cmf(self):
result = pandas_ta.cmf(self.high, self.low, self.close, self.volume_)
+1 -1
View File
@@ -40,7 +40,7 @@ class TestVolumeExtension(TestCase):
def test_aobv_ext(self):
self.data.ta.aobv(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-7:]), ["OBV", "OBV_min_2", "OBV_max_2", "OBV_EMA_2", "OBV_EMA_4", "AOBV_LR_2", "AOBV_SR_2"])
self.assertEqual(list(self.data.columns[-7:]), ["OBV", "OBV_min_2", "OBV_max_2", "OBV_EMA_4", "OBV_EMA_12", "AOBV_LR_2", "AOBV_SR_2"])
# Remove "OBV" so it does not interfere with test_obv_ext()
self.data.drop("OBV", axis=1, inplace=True)