mirror of
https://github.com/wassname/pandas-ta.git
synced 2026-08-18 12:20:24 +08:00
ENH #262 cdl_z and cti DOC update MAINT refactoring
This commit is contained in:
@@ -39,9 +39,9 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever
|
||||
* [DataFrame Properties](#dataframe-properties)
|
||||
* [DataFrame Methods](#dataframe-methods)
|
||||
* [Indicators by Category](#indicators-by-category)
|
||||
* [Candles](#candles-63)
|
||||
* [Candles](#candles-64)
|
||||
* [Cycles](#cycles-1)
|
||||
* [Momentum](#momentum-38)
|
||||
* [Momentum](#momentum-39)
|
||||
* [Overlap](#overlap-31)
|
||||
* [Performance](#performance-4)
|
||||
* [Statistics](#statistics-9)
|
||||
@@ -97,7 +97,7 @@ $ pip install pandas_ta
|
||||
|
||||
Latest Version
|
||||
--------------
|
||||
Best choice! Version: *0.2.69b*
|
||||
Best choice! Version: *0.2.70b*
|
||||
```sh
|
||||
$ pip install -U git+https://github.com/twopirllc/pandas-ta
|
||||
```
|
||||
@@ -632,6 +632,7 @@ Patterns that are **not bold**, require TA-Lib to be installed: ```pip install T
|
||||
* upsidegap2crows
|
||||
* xsidegap3methods
|
||||
* _Heikin-Ashi_: **ha**
|
||||
* _Z Score_: **cdl_z**
|
||||
```python
|
||||
# Get all candle patterns (This is the default behaviour)
|
||||
df = df.ta.cdl_pattern(name="all")
|
||||
@@ -668,6 +669,8 @@ df.ta.cdl(["doji", "inside"], append=True)
|
||||
* _Center of Gravity_: **cg**
|
||||
* _Chande Momentum Oscillator_: **cmo**
|
||||
* _Coppock Curve_: **coppock**
|
||||
* _Correlation Trend Indicator_: **cti**
|
||||
* A wrapper for ```ta.linreg(series, r=True)```
|
||||
* _Efficiency Ratio_: **er**
|
||||
* _Elder Ray Index_: **eri**
|
||||
* _Fisher Transform_: **fisher**
|
||||
@@ -907,6 +910,8 @@ result = ta.cagr(df.close)
|
||||
* _Arnaud Legoux Moving Average_ (**alma**) uses the curve of the Normal (Gauss) distribution to allow regulating the smoothness and high sensitivity of the indicator. See: ```help(ta.alma)```
|
||||
trading account, or fund. See: ```help(ta.drawdown)```
|
||||
* _Candle Patterns_ (**cdl_pattern**) If TA Lib is installed, then all those Candle Patterns are available. See the list and examples above on how to call the patterns. See: ```help(ta.cdl_pattern)```
|
||||
* _Candle Z Score_ (**cdl_z**) normalizes OHLC Candles with a rolling Z Score. See: ```help(ta.cdl_z)```
|
||||
* _Correlation Trend Indicator_ (**cti**) is an oscillator created by John Ehler in 2020. See: ```help(ta.cti)```
|
||||
* _Even Better Sinewave_ (**ebsw**) measures market cycles and uses a low pass filter to remove noise. See: ```help(ta.ebsw)```
|
||||
* _Schaff Trend Cycle_ (**stc**) is an evolution of the popular MACD incorportating two
|
||||
cascaded stochastic calculations with additional smoothing. See: ```help(ta.stc)```
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -40,17 +40,17 @@ Imports = {
|
||||
Category = {
|
||||
# Candles
|
||||
"candles": [
|
||||
"cdl", "cdl_pattern", "ha"
|
||||
"cdl", "cdl_pattern", "cdl_z", "ha"
|
||||
],
|
||||
# Cycles
|
||||
"cycles": ["ebsw"],
|
||||
# Momentum
|
||||
"momentum": [
|
||||
"ao", "apo", "bias", "bop", "brar", "cci", "cfo", "cg", "cmo",
|
||||
"coppock", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd",
|
||||
"coppock", "cti", "er", "eri", "fisher", "inertia", "kdj", "kst", "macd",
|
||||
"mom", "pgo", "ppo", "psl", "pvo", "qqe", "roc", "rsi", "rsx", "rvgi",
|
||||
"slope", "smi", "squeeze", "stc", "stoch", "stochrsi", "td_seq", "trix", "tsi", "uo",
|
||||
"willr"
|
||||
"slope", "smi", "squeeze", "stc", "stoch", "stochrsi", "td_seq", "trix",
|
||||
"tsi", "uo", "willr"
|
||||
],
|
||||
# Overlap
|
||||
"overlap": [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .ha import ha
|
||||
from .cdl_doji import cdl_doji
|
||||
from .cdl_inside import cdl_inside
|
||||
from .cdl_pattern import cdl_pattern, cdl, ALL_PATTERNS as CDL_PATTERN_NAMES
|
||||
from .cdl_z import cdl_z
|
||||
from .ha import ha
|
||||
@@ -0,0 +1,92 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pandas import DataFrame
|
||||
from pandas_ta.statistics import zscore
|
||||
from pandas_ta.utils import get_offset, verify_series
|
||||
|
||||
|
||||
def cdl_z(open_, high, low, close, length=None, full=None, ddof=None, offset=None, **kwargs):
|
||||
"""Candle Type: Z Score"""
|
||||
# Validate Arguments
|
||||
length = int(length) if length and length > 0 else 30
|
||||
ddof = int(ddof) if ddof and ddof >= 0 and ddof < length else 1
|
||||
open_ = verify_series(open_, length)
|
||||
high = verify_series(high, length)
|
||||
low = verify_series(low, length)
|
||||
close = verify_series(close, length)
|
||||
offset = get_offset(offset)
|
||||
full = bool(full) if full is not None and full else False
|
||||
|
||||
if open_ is None or high is None or low is None or close is None: return
|
||||
|
||||
# Calculate Result
|
||||
if full:
|
||||
length = close.size
|
||||
|
||||
z_open = zscore(open_, length=length, ddof=ddof)
|
||||
z_high = zscore(high, length=length, ddof=ddof)
|
||||
z_low = zscore(low, length=length, ddof=ddof)
|
||||
z_close = zscore(close, length=length, ddof=ddof)
|
||||
|
||||
_full = "a" if full else ""
|
||||
_props = _full if full else f"_{length}_{ddof}"
|
||||
df = DataFrame({
|
||||
f"open_Z{_props}": z_open,
|
||||
f"high_Z{_props}": z_high,
|
||||
f"low_Z{_props}": z_low,
|
||||
f"close_Z{_props}": z_close,
|
||||
})
|
||||
|
||||
if full:
|
||||
df.fillna(method="backfill", axis=0, inplace=True)
|
||||
|
||||
# Offset
|
||||
if offset != 0:
|
||||
df = df.shift(offset)
|
||||
|
||||
# Handle fills
|
||||
if "fillna" in kwargs:
|
||||
df.fillna(kwargs["fillna"], inplace=True)
|
||||
if "fill_method" in kwargs:
|
||||
df.fillna(method=kwargs["fill_method"], inplace=True)
|
||||
|
||||
# Name and Categorize it
|
||||
df.name = f"CDL_Z{_props}"
|
||||
df.category = "candles"
|
||||
|
||||
return df
|
||||
|
||||
|
||||
cdl_z.__doc__ = \
|
||||
"""Candle Type: Z
|
||||
|
||||
Normalizes OHLC Candles with a rolling Z Score.
|
||||
|
||||
Source: Kevin Johnson
|
||||
|
||||
Calculation:
|
||||
Default values:
|
||||
length=30, full=False, ddof=1
|
||||
Z = ZSCORE
|
||||
|
||||
open = Z( open, length, ddof)
|
||||
high = Z( high, length, ddof)
|
||||
low = Z( low, length, ddof)
|
||||
close = Z(close, length, ddof)
|
||||
|
||||
Args:
|
||||
open_ (pd.Series): Series of 'open's
|
||||
high (pd.Series): Series of 'high's
|
||||
low (pd.Series): Series of 'low's
|
||||
close (pd.Series): Series of 'close's
|
||||
length (int): The period. Default: 10
|
||||
|
||||
Kwargs:
|
||||
naive (bool, optional): If True, prefills potential Doji less than
|
||||
the length if less than a percentage of it's high-low range.
|
||||
Default: False
|
||||
fillna (value, optional): pd.DataFrame.fillna(value)
|
||||
fill_method (value, optional): Type of fill method
|
||||
|
||||
Returns:
|
||||
pd.Series: CDL_DOJI column.
|
||||
"""
|
||||
+14
-1
@@ -842,7 +842,15 @@ class AnalysisIndicators(BasePandasObject):
|
||||
result = cdl_pattern(open_=open_, high=high, low=low, close=close, name=name, offset=offset, **kwargs)
|
||||
return self._post_process(result, **kwargs)
|
||||
|
||||
cdl = cdl_pattern
|
||||
cdl = cdl_pattern # Alias for cdl_pattern
|
||||
|
||||
def cdl_z(self, full=None, offset=None, **kwargs):
|
||||
open_ = self._get_column(kwargs.pop("open", "open"))
|
||||
high = self._get_column(kwargs.pop("high", "high"))
|
||||
low = self._get_column(kwargs.pop("low", "low"))
|
||||
close = self._get_column(kwargs.pop("close", "close"))
|
||||
result = cdl_z(open_=open_, high=high, low=low, close=close, full=full, offset=offset, **kwargs)
|
||||
return self._post_process(result, **kwargs)
|
||||
|
||||
def ha(self, offset=None, **kwargs):
|
||||
open_ = self._get_column(kwargs.pop("open", "open"))
|
||||
@@ -918,6 +926,11 @@ class AnalysisIndicators(BasePandasObject):
|
||||
result = coppock(close=close, length=length, fast=fast, slow=slow, offset=offset, **kwargs)
|
||||
return self._post_process(result, **kwargs)
|
||||
|
||||
def cti(self, length=None, offset=None, **kwargs):
|
||||
close = self._get_column(kwargs.pop("close", "close"))
|
||||
result = cti(close=close, length=length, offset=offset, **kwargs)
|
||||
return self._post_process(result, **kwargs)
|
||||
|
||||
def er(self, length=None, drift=None, offset=None, **kwargs):
|
||||
close = self._get_column(kwargs.pop("close", "close"))
|
||||
result = er(close=close, length=length, drift=drift, offset=offset, **kwargs)
|
||||
|
||||
+23
-47
@@ -1,70 +1,46 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
# -*- coding: utf-8 -*-
|
||||
from pandas import Series
|
||||
from pandas_ta.overlap import linreg
|
||||
from pandas_ta.utils import get_offset, verify_series
|
||||
|
||||
|
||||
def cti(close: pd.Series, length: int, offset=None, **kwargs) -> pd.Series:
|
||||
def cti(close, length=None, offset=None, **kwargs) -> Series:
|
||||
"""Indicator: Correlation Trend Indicator"""
|
||||
close = verify_series(close)
|
||||
length = int(length) if length and length > 0 else 12
|
||||
close = verify_series(close, length)
|
||||
offset = get_offset(offset)
|
||||
|
||||
def _cti(series: pd.Series) -> float:
|
||||
"""
|
||||
Provide cell CTI value for numpy strides.
|
||||
if close is None: return
|
||||
|
||||
Args:
|
||||
series (pd.Series): Rolling window of pd.Series.
|
||||
|
||||
Returns:
|
||||
float: Value for cell.
|
||||
"""
|
||||
r = np.arange(0, length)
|
||||
sx = sum(series)
|
||||
sy = -sum(r)
|
||||
sxx = sum(np.square(series))
|
||||
sxy = sum(series * r * -1)
|
||||
syy = sum(r ** 2)
|
||||
|
||||
x_denom = length * sxx - sx ** 2
|
||||
y_denom = length * syy - sy ** 2
|
||||
if x_denom > 0 and y_denom > 0:
|
||||
return ((length * sxy - sx * sy) / (x_denom * y_denom) ** 0.5) * -1
|
||||
return 0
|
||||
|
||||
values = [
|
||||
_cti(each)
|
||||
for each in np.lib.stride_tricks.sliding_window_view(np.array(close), length)
|
||||
]
|
||||
cti_ds = pd.Series([np.NaN] * (length - 1) + values)
|
||||
cti_ds.index = close.index
|
||||
cti = linreg(close, length=length, r=True)
|
||||
|
||||
# Offset
|
||||
if offset != 0:
|
||||
cti_ds = cti_ds.shift(offset)
|
||||
cti = cti.shift(offset)
|
||||
|
||||
# Handle fills
|
||||
if "fillna" in kwargs:
|
||||
cti_ds.fillna(method=kwargs["fillna"], inplace=True)
|
||||
cti.fillna(method=kwargs["fillna"], inplace=True)
|
||||
if "fill_method" in kwargs:
|
||||
cti_ds.fillna(method=kwargs["fill_method"], inplace=True)
|
||||
cti.fillna(method=kwargs["fill_method"], inplace=True)
|
||||
|
||||
cti_ds.name = f"CTI_{length}"
|
||||
cti_ds.category = "momentum"
|
||||
return cti_ds
|
||||
cti.name = f"CTI_{length}"
|
||||
cti.category = "momentum"
|
||||
return cti
|
||||
|
||||
|
||||
cti.__doc__ = """
|
||||
The Correlation Trend Indicator is an oscillating technical indicator created
|
||||
by John Ehler in 2020. Assigns a value depending on how close prices in that
|
||||
range are to following a positively- or negatively-sloping straight line.
|
||||
Values range from -1 to 1.
|
||||
cti.__doc__ = \
|
||||
"""Correlation Trend Indicator (CTI)
|
||||
|
||||
The Correlation Trend Indicator is an oscillator created by John Ehler in 2020.
|
||||
It assigns a value depending on how close prices in that range are to following
|
||||
a positively- or negatively-sloping straight line. Values range from -1 to 1.
|
||||
This is a wrapper for ta.linreg(close, r=True).
|
||||
|
||||
Args:
|
||||
close (pd.Series): The dataseries of close prices for the selected instrument.
|
||||
length (int): The window to be taking values from for the indicator. Default is 12.
|
||||
offset ([type], optional): If there is an offset of the series to be applied.
|
||||
Default is None.
|
||||
close (pd.Series): Series of 'close's
|
||||
length (int): It's period. Default: 12
|
||||
offset (int): How many periods to offset the result. Default: 0
|
||||
|
||||
Returns:
|
||||
pd.Series: Series of the CTI values for the given period.
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from numpy import array as npArray
|
||||
from numpy import arctan as npAtan
|
||||
from numpy import NaN as npNaN
|
||||
from numpy import pi as npPi
|
||||
from numpy import sqrt as npSqrt
|
||||
from numpy.lib.stride_tricks import sliding_window_view
|
||||
from pandas import Series
|
||||
from pandas_ta.utils import get_offset, verify_series
|
||||
|
||||
|
||||
@@ -46,12 +49,13 @@ def linreg(close, length=None, offset=None, **kwargs):
|
||||
if r:
|
||||
y2_sum = (series * series).sum()
|
||||
rn = length * xy_sum - x_sum * y_sum
|
||||
rd = npSqrt(divisor * (length * y2_sum - y_sum * y_sum))
|
||||
rd = (divisor * (length * y2_sum - y_sum * y_sum)) ** 0.5
|
||||
return rn / rd
|
||||
|
||||
return m * length + b if tsf else m * (length - 1) + b
|
||||
|
||||
linreg = close.rolling(length, min_periods=length).apply(linear_regression, raw=False)
|
||||
linreg_ = [linear_regression(_) for _ in sliding_window_view(npArray(close), length)]
|
||||
linreg = Series([npNaN] * (length - 1) + linreg_, index=close.index)
|
||||
|
||||
# Offset
|
||||
if offset != 0:
|
||||
@@ -69,6 +73,7 @@ def linreg(close, length=None, offset=None, **kwargs):
|
||||
if intercept: linreg.name += "b"
|
||||
if angle: linreg.name += "a"
|
||||
if r: linreg.name += "r"
|
||||
|
||||
linreg.name += f"_{length}"
|
||||
linreg.category = "overlap"
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ from .variance import variance
|
||||
from pandas_ta.utils import get_offset, verify_series
|
||||
|
||||
|
||||
def stdev(close, length=None, ddof=1, offset=None, **kwargs):
|
||||
def stdev(close, length=None, ddof=None, offset=None, **kwargs):
|
||||
"""Indicator: Standard Deviation"""
|
||||
# Validate Arguments
|
||||
length = int(length) if length and length > 0 else 30
|
||||
ddof = int(ddof) if ddof >= 0 and ddof < length else 1
|
||||
ddof = int(ddof) if ddof and ddof >= 0 and ddof < length else 1
|
||||
close = verify_series(close, length)
|
||||
offset = get_offset(offset)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from packaging import version
|
||||
from pandas import DataFrame
|
||||
from pandas_ta import Imports, RATE, version
|
||||
from ._core import _camelCase2Title
|
||||
@@ -134,17 +133,6 @@ def yf(ticker: str, **kwargs):
|
||||
except KeyError as ke:
|
||||
print(f"[X] Ticker '{ticker}' not found.")
|
||||
return
|
||||
# print(f"[X] ticker_info[{type(ticker_info)}:{len(ticker_info.keys())}]\n{ticker_info}\n")
|
||||
|
||||
try:
|
||||
infodf = DataFrame.from_dict(ticker_info, orient="index")
|
||||
except TypeError as te:
|
||||
print(f"[X] TypeError: {te}")
|
||||
|
||||
# print(f"[X] infodf.empty: {infodf.empty}")
|
||||
if infodf.empty: return
|
||||
# print(f"[X] infodf[{type(infodf)}:{len(infodf.keys())}]\n{infodf}\n")
|
||||
infodf.name, infodf.columns = ticker, [ticker]
|
||||
|
||||
# Dividends and Splits
|
||||
dividends, splits = yfd.splits, yfd.dividends
|
||||
@@ -156,7 +144,7 @@ def yf(ticker: str, **kwargs):
|
||||
|
||||
print("\n==== Company Information " + div)
|
||||
print(f"{ticker_info['longName']} ({ticker_info['shortName']}) [{ticker_info['symbol']}]")
|
||||
print(f"[i] {type(ticker_info['longBusinessSummary'])}: {ticker_info['longBusinessSummary']}")
|
||||
|
||||
if description:
|
||||
print(f"{ticker_info['longBusinessSummary']}\n")
|
||||
if "address1" in ticker_info and len(ticker_info["address1"]):
|
||||
|
||||
@@ -3,7 +3,7 @@ from datetime import datetime
|
||||
from time import localtime, perf_counter
|
||||
from typing import Tuple
|
||||
|
||||
from pandas import DataFrame, DatetimeIndex, Timestamp
|
||||
from pandas import DataFrame, Timestamp
|
||||
|
||||
from pandas_ta import EXCHANGE_TZ, RATE
|
||||
from pandas_ta.utils import verify_series
|
||||
|
||||
@@ -18,7 +18,7 @@ setup(
|
||||
"pandas_ta.volatility",
|
||||
"pandas_ta.volume"
|
||||
],
|
||||
version=".".join(("0", "2", "69b")),
|
||||
version=".".join(("0", "2", "70b")),
|
||||
description=long_description,
|
||||
long_description=long_description,
|
||||
author="Kevin Johnson",
|
||||
|
||||
@@ -28,6 +28,11 @@ class TestCandleExtension(TestCase):
|
||||
self.assertIsInstance(self.data, DataFrame)
|
||||
self.assertEqual(self.data.columns[-1], "CDL_INSIDE")
|
||||
|
||||
def test_cdl_z_ext(self):
|
||||
self.data.ta.cdl_z(append=True)
|
||||
self.assertIsInstance(self.data, DataFrame)
|
||||
self.assertEqual(list(self.data.columns[-4:]), ["open_Z_30_1", "high_Z_30_1", "low_Z_30_1", "close_Z_30_1"])
|
||||
|
||||
def test_ha_ext(self):
|
||||
self.data.ta.ha(append=True)
|
||||
self.assertIsInstance(self.data, DataFrame)
|
||||
|
||||
@@ -68,6 +68,11 @@ class TestMomentumExtension(TestCase):
|
||||
self.assertIsInstance(self.data, DataFrame)
|
||||
self.assertEqual(self.data.columns[-1], "COPC_11_14_10")
|
||||
|
||||
def test_cti_ext(self):
|
||||
self.data.ta.cti(append=True)
|
||||
self.assertIsInstance(self.data, DataFrame)
|
||||
self.assertEqual(self.data.columns[-1], "CTI_12")
|
||||
|
||||
def test_er_ext(self):
|
||||
self.data.ta.er(append=True)
|
||||
self.assertIsInstance(self.data, DataFrame)
|
||||
|
||||
@@ -73,3 +73,8 @@ class TestCandle(TestCase):
|
||||
result = pandas_ta.cdl_inside(self.open, self.high, self.low, self.close, asbool=True)
|
||||
self.assertIsInstance(result, Series)
|
||||
self.assertEqual(result.name, "CDL_INSIDE")
|
||||
|
||||
def test_cdl_z(self):
|
||||
result = pandas_ta.cdl_z(self.open, self.high, self.low, self.close)
|
||||
self.assertIsInstance(result, DataFrame)
|
||||
self.assertEqual(result.name, "CDL_Z_30_1")
|
||||
@@ -149,6 +149,11 @@ class TestMomentum(TestCase):
|
||||
self.assertIsInstance(result, Series)
|
||||
self.assertEqual(result.name, "COPC_11_14_10")
|
||||
|
||||
def test_cti(self):
|
||||
result = pandas_ta.cti(self.close)
|
||||
self.assertIsInstance(result, Series)
|
||||
self.assertEqual(result.name, "CTI_12")
|
||||
|
||||
def test_er(self):
|
||||
result = pandas_ta.er(self.close)
|
||||
self.assertIsInstance(result, Series)
|
||||
|
||||
Reference in New Issue
Block a user