diff --git a/.gitignore b/.gitignore
index 21e8f1f..475e306 100644
--- a/.gitignore
+++ b/.gitignore
@@ -119,6 +119,7 @@ pandas_ta/_wrapper.py
AlphaVantageAPI/
data/datas.csv
+data/f500.csv
data/GLD_D_tv.csv
data/SPY_5min.csv
data/SPY_1min.csv
diff --git a/Makefile b/Makefile
index bfd9a8e..659bbe4 100644
--- a/Makefile
+++ b/Makefile
@@ -16,16 +16,16 @@ init:
pip install -r requirements.txt
test_ext:
- python -m unittest -v tests/test_ext_indicator_*.py
+ python -m unittest -v -f tests/test_ext_indicator_*.py
test_metrics:
- python -m unittest -v tests/test_utils_metrics.py
+ python -m unittest -v -f tests/test_utils_metrics.py
test_strats:
- python -m unittest -v tests/test_strategy.py
+ python -m unittest -v -f tests/test_strategy.py
test_ta:
- python -m unittest -v tests/test_indicator_*.py
+ python -m unittest -v -f tests/test_indicator_*.py
test_utils:
- python -m unittest -v tests/test_utils.py
\ No newline at end of file
+ python -m unittest -v -f tests/test_utils.py
\ No newline at end of file
diff --git a/README.md b/README.md
index 405e68c..16a6051 100644
--- a/README.md
+++ b/README.md
@@ -39,11 +39,12 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever
* [Utility](#utility-5)
* [Volatility](#volatility-13)
* [Volume](#volume-13)
+* [Performance Metrics](#performance-metrics)
* [Changes](#changes)
- * [Recent](#recent)
- * [Breaking](#breaking)
- * [New](#new)
- * [Updated](#updated)
+ * [General](#general)
+ * [Breaking Indicators](#breaking-indicators)
+ * [New Indicators](#new-indicators)
+ * [Updated Indicators](#updated-indicators)
@@ -56,9 +57,11 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever
* Has 120+ indicators and utility functions.
* Indicators are tightly correlated with the de facto [TA Lib](https://mrjbq7.github.io/ta-lib/) if they share common indicators.
-* Have the need for speed? By using the _strategy_ method, you get **multiprocessing** for free!
+* Have the need for speed? By using the DataFrame _strategy_ method, you get **multiprocessing** for free!
* Easily add _prefixes_ or _suffixes_ or both to columns names. Useful for Custom Chained Strategies.
* 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)
+* **NEW** Performance Metrics
+
@@ -160,7 +163,7 @@ Thanks for trying **Pandas TA**!
_Thank you for your contributions!_
-[alexonab](https://github.com/alexonab) | [allahyarzadeh](https://github.com/allahyarzadeh) | [codesutras](https://github.com/codesutras) | [daikts](https://github.com/daikts) | [DrPaprikaa](https://github.com/DrPaprikaa) | [FGU1](https://github.com/FGU1) | [lluissalord](https://github.com/lluissalord) | [maxdignan](https://github.com/maxdignan) | [NkosenhleDuma](https://github.com/NkosenhleDuma) | [pbrumblay](https://github.com/pbrumblay) | [rluong003](https://github.com/rluong003) | [SoftDevDanial](https://github.com/SoftDevDanial) | [tg12](https://github.com/tg12) | [YuvalWein](https://github.com/YuvalWein)
+[alexonab](https://github.com/alexonab) | [allahyarzadeh](https://github.com/allahyarzadeh) | [codesutras](https://github.com/codesutras) | [daikts](https://github.com/daikts) | [DrPaprikaa](https://github.com/DrPaprikaa) | [FGU1](https://github.com/FGU1) | [lluissalord](https://github.com/lluissalord) | [maxdignan](https://github.com/maxdignan) | [NkosenhleDuma](https://github.com/NkosenhleDuma) | [pbrumblay](https://github.com/pbrumblay) | [RajeshDhalange](https://github.com/RajeshDhalange) | [rluong003](https://github.com/rluong003) | [SoftDevDanial](https://github.com/SoftDevDanial) | [tg12](https://github.com/tg12) | [YuvalWein](https://github.com/YuvalWein)
@@ -174,21 +177,21 @@ _Conventional_
You explicitly define the input columns and take care of the output.
* ```sma10 = ta.sma(df["Close"], length=10)```
- * Returns a series with name: ```SMA_10```
+ * Returns a Series with name: ```SMA_10```
* ```donchiandf = ta.donchian(df["HIGH"], df["low"], lower_length=10, upper_length=15)```
* Returns a DataFrame named ```DC_10_15``` and column names: ```DCL_10_15, DCM_10_15, DCU_10_15```
* ```ema10_ohlc4 = ta.ema(ta.ohlc4(df["Open"], df["High"], df["Low"], df["Close"]), length=10)```
* Conventional Chaining is possible but more explicit.
- * Since it returns a series named ```EMA_10```. If needed, you may need to uniquely name it.
+ * Since it returns a Series named ```EMA_10```. If needed, you may need to uniquely name it.
_Pandas TA DataFrame Extension_
====================
Calling ```df.ta``` will automatically lowercase _OHLCVA_ to _ohlcva_: _open, high, low, close, volume_, _adj_close_. By default, ```df.ta``` will use the _ohlcva_ for the indicator arguments removing the need to specify input columns directly.
* ```sma10 = df.ta.sma(length=10)```
- * Returns a series with name: ```SMA_10```
+ * Returns a Series with name: ```SMA_10```
* ```ema10_ohlc4 = df.ta.ema(close=df.ta.ohlc4(), length=10, suffix="OHLC4")```
- * Returns a series with name: ```EMA_10_OHLC4```
+ * Returns a Series with name: ```EMA_10_OHLC4```
* Chaining Indicators _require_ specifying the input like: ```close=df.ta.ohlc4()```.
* ```donchiandf = df.ta.donchian(lower_length=10, upper_length=15)```
* Returns a DataFrame named ```DC_10_15``` and column names: ```DCL_10_15, DCM_10_15, DCU_10_15```
@@ -356,18 +359,18 @@ df.ta.strategy(NonMPStrategy)
## **adjusted**
```python
-# Set ta to default to an adjusted column, 'adj_close', overriding default 'close'
+# Set ta to default to an adjusted column, 'adj_close', overriding default 'close'.
df.ta.adjusted = "adj_close"
df.ta.sma(length=10, append=True)
-# To reset back to 'close', set adjusted back to None
+# To reset back to 'close', set adjusted back to None.
df.ta.adjusted = None
```
## **categories**
```python
-# List of Pandas TA categories
+# List of Pandas TA categories.
df.ta.categories
```
@@ -375,7 +378,7 @@ df.ta.categories
```python
# Set the number of cores to use for strategy multiprocessing
-# Defaults to the number of cpus you have
+# Defaults to the number of cpus you have.
df.ta.cores = 4
# Returns the number of cores you set or your default number of cpus.
@@ -386,36 +389,31 @@ df.ta.cores
```python
# The 'datetime_ordered' property returns True if the DataFrame
-# index is of Pandas datetime64 and df.index[0] < df.index[-1]
-# Otherwise it returns False
+# index is of Pandas datetime64 and df.index[0] < df.index[-1].
+# Otherwise it returns False.
df.ta.datetime_ordered
```
## **reverse**
```python
-# The 'datetime_ordered' property returns True if the DataFrame
-# index is of Pandas datetime64 and df.index[0] < df.index[-1]
-# Otherwise it returns False
-df.ta.datetime_ordered
-
# The 'reverse' is a helper property that returns the DataFrame
-# in reverse order
+# in reverse order.
df.ta.reverse
```
## **prefix & suffix**
```python
-# Applying a prefix to the name of an indicator
+# Applying a prefix to the name of an indicator.
prehl2 = df.ta.hl2(prefix="pre")
print(prehl2.name) # "pre_HL2"
-# Applying a suffix to the name of an indicator
+# Applying a suffix to the name of an indicator.
endhl2 = df.ta.hl2(suffix="post")
print(endhl2.name) # "HL2_post"
-# Applying a prefix and suffix to the name of an indicator
+# Applying a prefix and suffix to the name of an indicator.
bothhl2 = df.ta.hl2(prefix="pre", suffix="post")
print(bothhl2.name) # "pre_HL2_post"
```
@@ -613,22 +611,45 @@ Use parameter: cumulative=**True** for cumulative results.
|:--------:|
|  |
+
+
+# **Performance Metrics**
+_Performance Metrics_ are a **new** addition to the package. These metrics return a _float_ and are _not_ part of the _DataFrame_ Extension. They are called conventionally. For Example:
+```python
+import pandas_ta as ta
+result = ta.cagr(df.close)
+```
+
+### Available Metrics
+* _Compounded Annual Growth Rate_: **cagr**
+* _Calmar Ratio_: **calmar_ratio**
+* _Downside Deviation_: **downside_deviation**
+* _Jensen's Alpha_: **jensens_alpha**
+* _Log Max Drawdown_: **log_max_drawdown**
+* _Max Drawdown_: **max_drawdown**
+* _Pure Profit Score_: **pure_profit_score**
+* _Sharpe Ratio_: **sharpe_ratio**
+* _Sortino Ratio_: **sortino_ratio**
+* _Volatility_: **volatility**
+
+
# **Changes**
-## **Recent**
+## **General**
* A __Strategy__ Class to help name and group your favorite indicators.
* Some indicators have had their ```mamode``` _kwarg_ updated with more _moving average_ choices with the **Moving Average Utility** function ```ta.ma()```. For simplicity, all _choices_ are single source _moving averages_. This is primarily an internal utility used by indicators that have a ```mamode``` _kwarg_. This includes indicators: _accbands_, _amat_, _aobv_, _atr_, _bbands_, _bias_, _efi_, _hilo_, _kc_, _natr_, _qqe_, _rvi_, and _thermo_; the default ```mamode``` parameters have not changed. However, ```ta.ma()``` can be used by the user as well if needed. For more information: ```help(ta.ma)```
* **Moving Average Choices**: dema, ema, fwma, hma, linreg, midpoint, pwma, rma, sinwma, sma, swma, t3, tema, trima, vidya, wma, zlma.
* An _experimental_ and independent __Watchlist__ Class located in the [Examples](https://github.com/twopirllc/pandas-ta/tree/master/examples/watchlist.py) Directory that can be used in conjunction with the new __Strategy__ Class.
* _Linear Regression_ (**linear_regression**) is a new utility method for Simple Linear Regression using _Numpy_ or _Scikit Learn_'s implementation.
+
-## **Breaking**
+## **Breaking Indicators**
* _Bollinger Bands_ (**bbands**): New column 'bandwidth' appended to the returning DataFrame. See: ```help(ta.bbands)```
-## **New**
+## **New Indicators**
* _Drawdown_ (**drawdown**) It is a peak-to-trough decline during a specific period for an investment,
trading account, or fund. See: ```help(ta.drawdown)```
* _Gann High-Low Activator_ (**hilo**) The Gann High Low Activator Indicator was created by Robert Krausz in a 1998. See: ```help(ta.hilo)```
@@ -638,10 +659,13 @@ trading account, or fund. See: ```help(ta.drawdown)```
* _TTM Trend_ (**ttm_trend**). A trend indicator inspired from John Carter's book "Mastering the Trade" issue of Stocks & Commodities Magazine. It is a moving average based trend indicator consisting of two different simple moving averages. See: ```help(ta.ttm_trend)```
* _Variable Index Dynamic Average_ (**vidya**) A popular Dynamic Moving Average created by Tushar Chande. See: ```help(ta.vidya)```
-## **Updated**
+## **Updated Indicators**
* _Average True Range_ (**atr**): The default ```mamode``` is now "**RMA**" and with the same ```mamode``` options as TradingView. See ```help(ta.atr)```.
+* _Decreasing_ (**decreasing**): New argument ```strict``` checks if the series is continuously decreasing over period ```length```. Default: ```False```. See ```help(ta.decreasing)```.
+* _Increasing_ (**increasing**): New argument ```strict``` checks if the series is continuously increasing over period ```length```. Default: ```False```. See ```help(ta.increasing)```.
* _Trend Return_ (**trend_return**): Returns a DataFrame now instead of Series with pertinenet trade info for a _trend_. An example can be found in the [AI Example Notebook](https://github.com/twopirllc/pandas-ta/tree/master/examples/AIExample.ipynb). The notebook is still a work in progress and open to colloboration.
+
# **Sources**
* [Original TA-LIB](http://ta-lib.org/)
diff --git a/pandas_ta/core.py b/pandas_ta/core.py
index 35fc3a6..6534c9c 100644
--- a/pandas_ta/core.py
+++ b/pandas_ta/core.py
@@ -185,17 +185,17 @@ class AnalysisIndicators(BasePandasObject):
>>> ichimoku, span = ta.ichimoku(df["High"], df["Low"], df["Close"])
Args:
- kind (str, optional): Default: None. Kind is the 'name' of the indicator.
+ kind (str, optional): Default: None. Kind is the 'name' of the indicator.
It converts kind to lowercase before calling.
- timed (bool, optional): Default: False. Curious about the execution
+ timed (bool, optional): Default: False. Curious about the execution
speed?
kwargs: Extension specific modifiers.
- append (bool, optional): Default: False. When True, it appends the
+ append (bool, optional): Default: False. When True, it appends the
resultant column(s) to the DataFrame.
Returns:
- Most Indicators will return a Pandas Series. Others like MACD, BBANDS,
- KC, et al will return a Pandas DataFrame. Ichimoku on the other hand
+ Most Indicators will return a Pandas Series. Others like MACD, BBANDS,
+ KC, et al will return a Pandas DataFrame. Ichimoku on the other hand
will return two DataFrames, the Ichimoku DataFrame for the known period
and a Span DataFrame for the future of the Span values.
@@ -1239,9 +1239,9 @@ class AnalysisIndicators(BasePandasObject):
result = decay(close=close, length=length, mode=mode, offset=offset, **kwargs)
return self._post_process(result, **kwargs)
- def decreasing(self, length=None, asint=True, offset=None, **kwargs):
+ def decreasing(self, length=None, strict=None, asint=None, offset=None, **kwargs):
close = self._get_column(kwargs.pop("close", "close"))
- result = decreasing(close=close, length=length, asint=asint, offset=offset, **kwargs)
+ result = decreasing(close=close, length=length, strict=strict, asint=asint, offset=offset, **kwargs)
return self._post_process(result, **kwargs)
def dpo(self, length=None, centered=True, offset=None, **kwargs):
@@ -1249,9 +1249,9 @@ class AnalysisIndicators(BasePandasObject):
result = dpo(close=close, length=length, centered=centered, offset=offset, **kwargs)
return self._post_process(result, **kwargs)
- def increasing(self, length=None, asint=True, offset=None, **kwargs):
+ def increasing(self, length=None, strict=None, asint=None, offset=None, **kwargs):
close = self._get_column(kwargs.pop("close", "close"))
- result = increasing(close=close, length=length, asint=asint, offset=offset, **kwargs)
+ result = increasing(close=close, length=length, strict=strict, asint=asint, offset=offset, **kwargs)
return self._post_process(result, **kwargs)
def long_run(self, fast=None, slow=None, length=None, offset=None, **kwargs):
diff --git a/pandas_ta/momentum/ppo.py b/pandas_ta/momentum/ppo.py
index a3bcbf0..829a62a 100644
--- a/pandas_ta/momentum/ppo.py
+++ b/pandas_ta/momentum/ppo.py
@@ -14,7 +14,6 @@ def ppo(close, fast=None, slow=None, signal=None, scalar=None, offset=None, **kw
scalar = float(scalar) if scalar else 100
if slow < fast:
fast, slow = slow, fast
- min_periods = int(kwargs["min_periods"]) if "min_periods" in kwargs and kwargs["min_periods"] is not None else fast
offset = get_offset(offset)
# Calculate Result
diff --git a/pandas_ta/overlap/fwma.py b/pandas_ta/overlap/fwma.py
index 8e90fb1..8859217 100644
--- a/pandas_ta/overlap/fwma.py
+++ b/pandas_ta/overlap/fwma.py
@@ -7,7 +7,6 @@ def fwma(close, length=None, asc=None, offset=None, **kwargs):
# Validate Arguments
close = verify_series(close)
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
offset = get_offset(offset)
diff --git a/pandas_ta/overlap/linreg.py b/pandas_ta/overlap/linreg.py
index 54f53e6..f77e55d 100644
--- a/pandas_ta/overlap/linreg.py
+++ b/pandas_ta/overlap/linreg.py
@@ -8,7 +8,6 @@ def linreg(close, length=None, offset=None, **kwargs):
# Validate arguments
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
offset = get_offset(offset)
angle = kwargs.pop("angle", False)
intercept = kwargs.pop("intercept", False)
diff --git a/pandas_ta/statistics/entropy.py b/pandas_ta/statistics/entropy.py
index 9fb5996..c3e05ef 100644
--- a/pandas_ta/statistics/entropy.py
+++ b/pandas_ta/statistics/entropy.py
@@ -45,9 +45,9 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 10
- base (float): Logarithmic Base. Default: 2
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 10
+ base (float): Logarithmic Base. Default: 2
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/kurtosis.py b/pandas_ta/statistics/kurtosis.py
index e2ed56c..8b477f2 100644
--- a/pandas_ta/statistics/kurtosis.py
+++ b/pandas_ta/statistics/kurtosis.py
@@ -36,8 +36,8 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 30
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/mad.py b/pandas_ta/statistics/mad.py
index 3c871d8..8d813af 100644
--- a/pandas_ta/statistics/mad.py
+++ b/pandas_ta/statistics/mad.py
@@ -41,8 +41,8 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 30
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/median.py b/pandas_ta/statistics/median.py
index b58ba0d..023dbcb 100644
--- a/pandas_ta/statistics/median.py
+++ b/pandas_ta/statistics/median.py
@@ -39,8 +39,8 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 30
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/quantile.py b/pandas_ta/statistics/quantile.py
index 6c6e6d9..543c972 100644
--- a/pandas_ta/statistics/quantile.py
+++ b/pandas_ta/statistics/quantile.py
@@ -37,9 +37,9 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
- q (float): The quantile. Default: 0.5
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 30
+ q (float): The quantile. Default: 0.5
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/skew.py b/pandas_ta/statistics/skew.py
index 0920b16..560bbd4 100644
--- a/pandas_ta/statistics/skew.py
+++ b/pandas_ta/statistics/skew.py
@@ -42,8 +42,8 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 30
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/stdev.py b/pandas_ta/statistics/stdev.py
index 825f3e5..cc50662 100644
--- a/pandas_ta/statistics/stdev.py
+++ b/pandas_ta/statistics/stdev.py
@@ -39,11 +39,11 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
+ length (int): It's period. Default: 30
ddof (int): Delta Degrees of Freedom.
The divisor used in calculations is N - ddof,
where N represents the number of elements. Default: 1
- offset (int): How many periods to offset the result. Default: 0
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/variance.py b/pandas_ta/statistics/variance.py
index 31c4105..b61afb8 100644
--- a/pandas_ta/statistics/variance.py
+++ b/pandas_ta/statistics/variance.py
@@ -38,11 +38,11 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
+ length (int): It's period. Default: 30
ddof (int): Delta Degrees of Freedom.
The divisor used in calculations is N - ddof,
where N represents the number of elements. Default: 1
- offset (int): How many periods to offset the result. Default: 0
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/statistics/zscore.py b/pandas_ta/statistics/zscore.py
index 82c494a..9ff2897 100644
--- a/pandas_ta/statistics/zscore.py
+++ b/pandas_ta/statistics/zscore.py
@@ -44,9 +44,9 @@ Calculation:
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 30
- std (float): It's period. Default: 1
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 30
+ std (float): It's period. Default: 1
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/trend/decreasing.py b/pandas_ta/trend/decreasing.py
index 159cdc0..695ac2f 100644
--- a/pandas_ta/trend/decreasing.py
+++ b/pandas_ta/trend/decreasing.py
@@ -2,18 +2,27 @@
from pandas_ta.utils import get_offset, verify_series
-def decreasing(close, length=None, asint=True, strictly=False, offset=None, **kwargs):
+def decreasing(close, length=None, strict=None, asint=None, offset=None, **kwargs):
"""Indicator: Decreasing"""
# Validate Arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 1
+ strict = strict if isinstance(strict, bool) else False
+ asint = asint if isinstance(asint, bool) else True
offset = get_offset(offset)
+ def stricly_decreasing(series, n):
+ return all([i > j for i,j in zip(series[-n:], series[1:])])
+
# Calculate Result
- if strictly:
- decreasing = all(i > j for i, j in zip(close[-length:], close[1:]))
+ if strict:
+ # Returns value as float64? Have to cast to bool
+ decreasing = close.rolling(length, min_periods=length).apply(stricly_decreasing, args=(length,), raw=False)
+ decreasing.fillna(0, inplace=True)
+ decreasing = decreasing.astype(bool)
else:
decreasing = close.diff(length) < 0
+
if asint:
decreasing = decreasing.astype(int)
@@ -28,7 +37,7 @@ def decreasing(close, length=None, asint=True, strictly=False, offset=None, **kw
decreasing.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
- decreasing.name = f"DEC_{length}"
+ decreasing.name = f"{'S' if strict else ''}DEC_{length}"
decreasing.category = "trend"
return decreasing
@@ -37,22 +46,23 @@ def decreasing(close, length=None, asint=True, strictly=False, offset=None, **kw
decreasing.__doc__ = \
"""Decreasing
-Returns True or False if the series is decreasing over a periods. By default,
-it returns True and False as 1 and 0 respectively with kwarg 'asint'.
-
-Sources:
+Returns True if the series is decreasing over a period, False otherwise. If the kwarg 'strict' is True, it returns True if it is continuously decreasing over the period. When using the kwarg 'asint', then it returns 1 for True or 0 for False.
Calculation:
- decreasing = close.diff(length) < 0
+ if strict:
+ decreasing = all(i > j for i, j in zip(close[-length:], close[1:]))
+ else:
+ decreasing = close.diff(length) < 0
+
if asint:
decreasing = decreasing.astype(int)
Args:
close (pd.Series): Series of 'close's
- length (int): It's period. Default: 1
- asint (bool): Returns as binary. Default: True
- strictly (bool): If True check for strictly continuous decreasing Default: False
- offset (int): How many periods to offset the result. Default: 0
+ length (int): It's period. Default: 1
+ asint (bool): Returns as binary. Default: True
+ strict (bool): If True, checks if the series is continuously decreasing over the period. Default: False
+ offset (int): How many periods to offset the result. Default: 0
Kwargs:
fillna (value, optional): pd.DataFrame.fillna(value)
diff --git a/pandas_ta/trend/increasing.py b/pandas_ta/trend/increasing.py
index 57e9300..83b0373 100644
--- a/pandas_ta/trend/increasing.py
+++ b/pandas_ta/trend/increasing.py
@@ -2,22 +2,30 @@
from pandas_ta.utils import get_offset, verify_series
-def increasing(close, length=None, asint=True, strictly=False, offset=None, **kwargs):
+def increasing(close, length=None, strict=None, asint=None, offset=None, **kwargs):
"""Indicator: Increasing"""
# Validate Arguments
close = verify_series(close)
length = int(length) if length and length > 0 else 1
+ strict = strict if isinstance(strict, bool) else False
+ asint = asint if isinstance(asint, bool) else True
offset = get_offset(offset)
-
+
+ def stricly_increasing(series, n):
+ return all([i < j for i,j in zip(series[-n:], series[1:])])
+
# Calculate Result
- if strictly:
- increasing = all(i < j for i, j in zip(close[-length:], close[1:]))
+ if strict:
+ # Returns value as float64? Have to cast to bool
+ increasing = close.rolling(length, min_periods=length).apply(stricly_increasing, args=(length,), raw=False)
+ increasing.fillna(0, inplace=True)
+ increasing = increasing.astype(bool)
else:
increasing = close.diff(length) > 0
-
+
if asint:
increasing = increasing.astype(int)
-
+
# Offset
if offset != 0:
increasing = increasing.shift(offset)
@@ -29,35 +37,37 @@ def increasing(close, length=None, asint=True, strictly=False, offset=None, **kw
increasing.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
- increasing.name = f"INC_{length}"
+ increasing.name = f"{'S' if strict else ''}INC_{length}"
increasing.category = "trend"
return increasing
-increasing.__doc__ = """Increasing
+increasing.__doc__ = \
+"""Increasing
- Returns True or False if the series is increasing over a periods. By default,
- it returns True and False as 1 and 0 respectively with kwarg 'asint'.
-
- Sources:
-
- Calculation:
+Returns True if the series is increasing over a period, False otherwise. If the kwarg 'strict' is True, it returns True if it is continuously increasing over the period. When using the kwarg 'asint', then it returns 1 for True or 0 for False.
+
+Calculation:
+ if strict:
+ increasing = all(i < j for i, j in zip(close[-length:], close[1:]))
+ else:
increasing = close.diff(length) > 0
- if asint:
- increasing = increasing.astype(int)
-
- Args:
- close (pd.Series): Series of 'close's
- length (int): It's period. Default: 1
- asint (bool): Returns as binary. Default: True
- strictly (bool): If True check for strictly continuous increasing Default: False
- 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.
- """
+
+ if asint:
+ increasing = increasing.astype(int)
+
+Args:
+ close (pd.Series): Series of 'close's
+ length (int): It's period. Default: 1
+ asint (bool): Returns as binary. Default: True
+ strict (bool): If True, checks if the series is continuously increasing over the period. Default: False
+ 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.
+"""
diff --git a/pandas_ta/utils/_metrics.py b/pandas_ta/utils/_metrics.py
index a638f4e..947b648 100644
--- a/pandas_ta/utils/_metrics.py
+++ b/pandas_ta/utils/_metrics.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from numpy import arange as npArange
+# from numpy import arange as npArange
from numpy import log as npLog
from numpy import sqrt as npSqrt
from pandas import DataFrame, Series, Timedelta
@@ -11,15 +11,32 @@ from pandas_ta.performance import drawdown, log_return, percent_return
def cagr(close: Series) -> float:
- """Compounded Annual Growth Rate"""
+ """Compounded Annual Growth Rate
+
+ Args:
+ close (pd.Series): Series of 'close's
+
+ >>> result = ta.cagr(df.close)
+ """
close = verify_series(close)
start, end = close.iloc[0], close.iloc[-1]
return ((end / start) ** (1 / total_time(close))) - 1
-def calmar_ratio(close: Series, method: str = "percent", years: int = 3, log: bool = False) -> float:
+def calmar_ratio(close: Series, method: str = "percent", years: int = 3) -> float:
"""The Calmar Ratio is the percent Max Drawdown Ratio 'typically' over
- the past three years."""
+ the past three years.
+
+ Args:
+ close (pd.Series): Series of 'close's
+ method (str): Max DD calculation options: 'dollar', 'percent', 'log'. Default: 'dollar'
+ years (int): The positive number of years to use. Default: 3
+
+ >>> result = ta.calmar_ratio(close, method="percent", years=3)
+ """
+ if years <= 0:
+ print(f"[!] calmar_ratio 'years' argument must be greater than zero.")
+ return
close = verify_series(close)
n_years_ago = close.index[-1] - Timedelta(days=365.25 * years)
@@ -28,10 +45,18 @@ def calmar_ratio(close: Series, method: str = "percent", years: int = 3, log: bo
return cagr(close) / max_drawdown(close, method=method)
-def downside_deviation(returns: Series, benchmark_rate: float = 0.0, log: bool = False, tf: str = "years") -> float:
+def downside_deviation(returns: Series, benchmark_rate: float = 0.0, tf: str = "years") -> float:
"""Downside Deviation for the Sortino ratio.
Benchmark rate is assumed to be annualized. Adjusted according for the
- number of periods per year seen in the data."""
+ number of periods per year seen in the data.
+
+ Args:
+ close (pd.Series): Series of 'close's
+ benchmark_rate (float): Benchmark Rate to use. Default: 0.0
+ tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'. Default: 'years'
+
+ >>> result = ta.downside_deviation(returns, benchmark_rate=0.0, tf="years")
+ """
# For both de-annualizing the benchmark rate and annualizing result
returns = verify_series(returns)
days_per_year = returns.shape[0] / total_time(returns, tf)
@@ -44,8 +69,15 @@ def downside_deviation(returns: Series, benchmark_rate: float = 0.0, log: bool =
return downside_deviation * npSqrt(days_per_year)
-def jensens_alpha(returns:Series, benchmark_returns:Series) -> float:
- """Jensen's 'Alpha' of a series and a benchmark."""
+def jensens_alpha(returns: Series, benchmark_returns: Series) -> float:
+ """Jensen's 'Alpha' of a series and a benchmark.
+
+ Args:
+ returns (pd.Series): Series of 'returns's
+ benchmark_returns (pd.Series): Series of 'benchmark_returns's
+
+ >>> result = ta.jensens_alpha(returns, benchmark_returns)
+ """
returns = verify_series(returns)
benchmark_returns = verify_series(benchmark_returns)
@@ -53,15 +85,29 @@ def jensens_alpha(returns:Series, benchmark_returns:Series) -> float:
return linear_regression(benchmark_returns, returns)["a"]
-def log_max_drawdown(close:Series):
- """Log Max Drawdown of a series."""
+def log_max_drawdown(close: Series) -> float:
+ """Log Max Drawdown of a series.
+
+ Args:
+ close (pd.Series): Series of 'close's
+
+ >>> result = ta.log_max_drawdown(close)
+ """
close = verify_series(close)
log_return = npLog(close.iloc[-1]) - npLog(close.iloc[0])
return log_return - max_drawdown(close, method="log")
def max_drawdown(close: Series, method:str = None, all:bool = False) -> float:
- """Maximum Drawdown from close. Defaults to 'dollar'. """
+ """Maximum Drawdown from close. Default: 'dollar'.
+
+ Args:
+ close (pd.Series): Series of 'close's
+ method (str): Max DD calculation options: 'dollar', 'percent', 'log'. Default: 'dollar'
+ all (bool): If True, it returns all three methods as a dict. Default: False
+
+ >>> result = ta.max_drawdown(close, method="dollar", all=False)
+ """
close = verify_series(close)
max_dd = drawdown(close).max()
@@ -77,9 +123,15 @@ def max_drawdown(close: Series, method:str = None, all:bool = False) -> float:
return max_dd_["dollar"]
-def pure_profit_score(close:Series) -> float:
- """Pure Profit Score of a series."""
- from sklearn.linear_model import LinearRegression
+def pure_profit_score(close: Series) -> float:
+ """Pure Profit Score of a series.
+
+ Args:
+ close (pd.Series): Series of 'close's
+
+ >>> result = ta.pure_profit_score(df.close)
+ """
+ # from sklearn.linear_model import LinearRegression
close = verify_series(close)
close_index = Series(0, index=close.reset_index().index)
@@ -87,8 +139,16 @@ def pure_profit_score(close:Series) -> float:
return r * cagr(close)
-def sharpe_ratio(close:Series, benchmark_rate:float = 0.0, log:bool = False) -> float:
- """Sharpe Ratio of a series."""
+def sharpe_ratio(close: Series, benchmark_rate: float = 0.0, log: bool = False) -> float:
+ """Sharpe Ratio of a series.
+
+ Args:
+ close (pd.Series): Series of 'close's
+ benchmark_rate (float): Benchmark Rate to use. Default: 0.0
+ log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False
+
+ >>> result = ta.sharpe_ratio(close, benchmark_rate=0.0, log=False)
+ """
close = verify_series(close)
returns = percent_return(close=close) if not log else log_return(close=close)
@@ -97,8 +157,16 @@ def sharpe_ratio(close:Series, benchmark_rate:float = 0.0, log:bool = False) ->
return result
-def sortino_ratio(close:Series, benchmark_rate:float = 0.0, log:bool = False) -> float:
- """Sortino Ratio of a series."""
+def sortino_ratio(close: Series, benchmark_rate: float = 0.0, log: bool = False) -> float:
+ """Sortino Ratio of a series.
+
+ Args:
+ close (pd.Series): Series of 'close's
+ benchmark_rate (float): Benchmark Rate to use. Default: 0.0
+ log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False
+
+ >>> result = ta.sortino_ratio(close, benchmark_rate=0.0, log=False)
+ """
close = verify_series(close)
returns = percent_return(close=close) if not log else log_return(close=close)
@@ -107,12 +175,23 @@ def sortino_ratio(close:Series, benchmark_rate:float = 0.0, log:bool = False) ->
return result
-def volatility(close: Series, tf:str = "years", returns:bool = False, log: bool = False, **kwargs) -> float:
- """Volatility of a series. Default: 'years'"""
+def volatility(close: Series, tf: str = "years", returns: bool = False, log: bool = False, **kwargs) -> float:
+ """Volatility of a series. Default: 'years'
+
+ Args:
+ close (pd.Series): Series of 'close's
+ tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'. Default: 'years'
+ returns (bool): If True, then it replace the close Series with the user defined Series; typically user generated returns or percent returns or log returns. Default: False
+ log (bool): If True, calculates log_return. Otherwise it calculates percent_return. Default: False
+
+ >>> result = ta.volatility(close, tf="years", returns=False, log=False, **kwargs)
+ """
close = verify_series(close)
if not returns:
returns = percent_return(close=close) if not log else log_return(close=close)
+ else:
+ returns = close
factor = returns.shape[0] / total_time(returns, tf)
if kwargs.pop("nearest_day", False) and tf.lower() == "years":
diff --git a/setup.py b/setup.py
index 206db2b..867c740 100644
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@ setup(
"pandas_ta.volatility",
"pandas_ta.volume"
],
- version=".".join(("0", "2", "29b")),
+ version=".".join(("0", "2", "30b")),
description=long_description,
long_description=long_description,
author="Kevin Johnson",
diff --git a/tests/test_ext_indicator_trend.py b/tests/test_ext_indicator_trend.py
index 60a1c2e..6e73504 100644
--- a/tests/test_ext_indicator_trend.py
+++ b/tests/test_ext_indicator_trend.py
@@ -57,6 +57,10 @@ class TestTrendExtension(TestCase):
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "DEC_1")
+ self.data.ta.decreasing(length=3, strict=True, append=True)
+ self.assertIsInstance(self.data, DataFrame)
+ self.assertEqual(self.data.columns[-1], "SDEC_3")
+
def test_dpo_ext(self):
self.data.ta.dpo(append=True)
self.assertIsInstance(self.data, DataFrame)
@@ -67,6 +71,10 @@ class TestTrendExtension(TestCase):
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(self.data.columns[-1], "INC_1")
+ self.data.ta.increasing(length=3, strict=True, append=True)
+ self.assertIsInstance(self.data, DataFrame)
+ self.assertEqual(self.data.columns[-1], "SINC_3")
+
def test_long_run_ext(self):
# Nothing passed, return self
self.assertEqual(self.data.ta.long_run(append=True).shape, self.data.shape)
diff --git a/tests/test_indicator_trend.py b/tests/test_indicator_trend.py
index 0b2017c..c644b1d 100644
--- a/tests/test_indicator_trend.py
+++ b/tests/test_indicator_trend.py
@@ -113,6 +113,10 @@ class TestTrend(TestCase):
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "DEC_1")
+ result = pandas_ta.decreasing(self.close, length=3, strict=True)
+ self.assertIsInstance(result, Series)
+ self.assertEqual(result.name, "SDEC_3")
+
def test_dpo(self):
result = pandas_ta.dpo(self.close)
self.assertIsInstance(result, Series)
@@ -123,6 +127,10 @@ class TestTrend(TestCase):
self.assertIsInstance(result, Series)
self.assertEqual(result.name, "INC_1")
+ result = pandas_ta.increasing(self.close, length=3, strict=True)
+ self.assertIsInstance(result, Series)
+ self.assertEqual(result.name, "SINC_3")
+
def test_long_run(self):
result = pandas_ta.long_run(self.close, self.open)
self.assertIsInstance(result, Series)
diff --git a/tests/test_utils_metrics.py b/tests/test_utils_metrics.py
index db6233b..95d7d76 100644
--- a/tests/test_utils_metrics.py
+++ b/tests/test_utils_metrics.py
@@ -36,6 +36,12 @@ class TestUtilityMetrics(TestCase):
self.assertIsInstance(result, float)
self.assertGreaterEqual(result, 0)
+ result = pandas_ta.calmar_ratio(self.close, years=0)
+ self.assertIsNone(result)
+
+ result = pandas_ta.calmar_ratio(self.close, years=-2)
+ self.assertIsNone(result)
+
def test_downside_deviation(self):
result = pandas_ta.downside_deviation(self.pctret)
self.assertIsInstance(result, float)
@@ -101,6 +107,11 @@ class TestUtilityMetrics(TestCase):
self.assertGreaterEqual(result, 0)
def test_volatility(self):
+ returns_ = pandas_ta.percent_return(self.close)
+ result = pandas_ta.utils.volatility(returns_, returns=True)
+ self.assertIsInstance(result, float)
+ self.assertGreaterEqual(result, 0)
+
for tf in ["years", "months", "weeks", "days", "hours", "minutes", "seconds"]:
result = pandas_ta.utils.volatility(self.close, tf)
self.assertIsInstance(result, float)