ENH #267 stc added with refactoring DOC readme updates TST stc added

This commit is contained in:
Kevin Johnson
2021-04-11 09:21:01 -07:00
parent 5937576421
commit 3afd74564f
7 changed files with 140 additions and 100 deletions
+17 -10
View File
@@ -41,7 +41,7 @@ _Pandas Technical Analysis_ (**Pandas TA**) is an easy to use library that lever
* [Indicators by Category](#indicators-by-category)
* [Candles](#candles-63)
* [Cycles](#cycles-1)
* [Momentum](#momentum-37)
* [Momentum](#momentum-38)
* [Overlap](#overlap-31)
* [Performance](#performance-4)
* [Statistics](#statistics-9)
@@ -97,7 +97,7 @@ $ pip install pandas_ta
Latest Version
--------------
Best choice! Version: *0.2.68b*
Best choice! Version: *0.2.69b*
```sh
$ pip install -U git+https://github.com/twopirllc/pandas-ta
```
@@ -567,10 +567,6 @@ help(ta.yf)
# **Indicators** (_by Category_)
### **Candles** (63)
_Candle Patterns_: ```ta.cdl_pattern``` or ```ta.cdl```
Patterns that are **not bold**, require TA-Lib to be installed: ```pip install TA-Lib```
* 2crows
@@ -657,12 +653,11 @@ df.ta.cdl(["doji", "inside"], append=True)
### **Cycles** (1)
* _Even Better Sinewave_: **ebsw**
<br/>
### **Momentum** (37)
### **Momentum** (38)
* _Awesome Oscillator_: **ao**
* _Absolute Price Oscillator_: **apo**
* _Bias_: **bias**
@@ -708,6 +703,7 @@ df.ta.cdl(["doji", "inside"], append=True)
| _Moving Average Convergence Divergence_ (MACD) |
|:--------:|
| ![Example MACD](/images/SPY_MACD.png) |
<br/>
### **Overlap** (31)
@@ -752,8 +748,8 @@ df.ta.cdl(["doji", "inside"], append=True)
| _Simple Moving Averages_ (SMA) and _Bollinger Bands_ (BBANDS) |
|:--------:|
| ![Example Chart](/images/TA_Chart.png) |
<br/>
<br/>
### **Performance** (4)
@@ -811,6 +807,8 @@ Use parameter: cumulative=**True** for cumulative results.
|:--------:|
| ![Example ADX](/images/SPY_ADX.png) |
<br/>
### **Utility** (5)
* _Above_: **above**
@@ -819,6 +817,8 @@ Use parameter: cumulative=**True** for cumulative results.
* _Below Value_: **below_value**
* _Cross_: **cross**
<br/>
### **Volatility** (13)
* _Aberration_: **aberration**
@@ -839,6 +839,8 @@ Use parameter: cumulative=**True** for cumulative results.
|:--------:|
| ![Example ATR](/images/SPY_ATR.png) |
<br/>
### **Volume** (14)
* _Accumulation/Distribution Index_: **ad**
@@ -899,12 +901,17 @@ result = ta.cagr(df.close)
## **Breaking Indicators**
* _Trend Return_ (**trend_return**) when given a trend Series like ```close > sma(close, 50)``` it now returns by default log and cumulative log returns of the trend as well as the Trends, Trades, Trade Entries and Trade Exits of that trend. Now compatible with [**vectorbt**](https://github.com/polakowo/vectorbt) by setting ```asbool=True``` to get boolean Trade Entries and Exits. See: ```help(ta.trend_return)```
<br/>
## **New Indicators**
* _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)```
* _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)```
* _Tom DeMark's Sequential_ (**td_seq**) attempts to identify a price point where an uptrend or a downtrend exhausts itself and reverses. Currently exlcuded from ```df.ta.strategy()``` for performance reasons. See: ```help(ta.td_seq)```
<br/>
## **Updated Indicators**
+2 -2
View File
@@ -1039,9 +1039,9 @@ class AnalysisIndicators(BasePandasObject):
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 self._post_process(result, **kwargs)
def stc(self, ma1=None, ma2=None, osc=None, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwargs):
def stc(self, ma1=None, ma2=None, osc=None, tclength=None, fast=None, slow=None, factor=None, offset=None, **kwargs):
close = self._get_column(kwargs.pop("close", "close"))
result = stc(close=close, ma1=ma1, ma2=ma2, osc=osc, tclen=tclen, fast=fast, slow=slow, factor=factor, offset=offset, **kwargs)
result = stc(close=close, ma1=ma1, ma2=ma2, osc=osc, tclength=tclength, fast=fast, slow=slow, factor=factor, offset=offset, **kwargs)
return self._post_process(result, **kwargs)
def stoch(self, fast_k=None, slow_k=None, slow_d=None, offset=None, **kwargs):
+83 -75
View File
@@ -1,96 +1,64 @@
# -*- coding: utf-8 -*-
from pandas import DataFrame, Series, concat
from pandas import DataFrame, Series
from pandas_ta.overlap import ema
from pandas_ta.utils import get_offset, verify_series, signals
from pandas_ta.utils import get_offset, non_zero_range, verify_series
def schaff_tc(close, XMAC, tclen, factor):
# ACTUAL Calculation part, which is shared between operation modes
# 1St : Stochastic of MACD
Value1 = XMAC.rolling(tclen).min() # min value in interval tclen
Value2 = XMAC.rolling(tclen).max() - Value1 # max value in interval tclen
# ... : %Fast K of MACD
Frac1 = list(XMAC)
Frac1[0] = 0
PF = list(XMAC)
PF[0] = 0
for i in range(1, len(XMAC)):
if Value1[i] > 0:
Frac1[i] = ((XMAC[i] - Value1[i]) / Value2[i]) * 100
else:
Frac1[i] = Frac1[i - 1]
# Smoothed Calculation for % Fast D of MACD
PF[i] = round(PF[i - 1] + (factor * (Frac1[i] - PF[i - 1])), 8)
PF = Series(PF, index=close.index)
# 2nd : Stochastic of smoothed Percent Fast D, 'PF', above
Value3 = PF.rolling(tclen).min() # min value in interval tclen
Value4 = PF.rolling(tclen).max() - Value3 # max value in interval tclen
# ... : % of Fast K of PF
Frac2 = list(XMAC)
Frac2[0] = 0
PFF = list(XMAC)
PFF[0] = 0
for i in range(1, len(XMAC)):
if Value4[i] > 0:
Frac2[i] = ((PF[i] - Value3[i]) / Value4[i]) * 100
else:
Frac2[i] = Frac2[i - 1]
# Smoothed Calculation for % Fast D of MACD
PFF[i] = round(PFF[i - 1] + (factor * (Frac2[i] - PFF[i - 1])), 8)
return [PFF, PF]
def stc(close, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwargs):
def stc(close, tclength=None, fast=None, slow=None, factor=None, offset=None, **kwargs):
"""Indicator: Schaff Trend Cycle (STC)"""
# Validate arguments
close = verify_series(close) # close
tclen = int(tclen) if tclen and tclen > 0 else 10
tclength = int(tclength) if tclength and tclength > 0 else 10
fast = int(fast) if fast and fast > 0 else 12
slow = int(slow) if slow and slow > 0 else 26
factor = float(factor) if factor and factor > 0 else 0.5
if slow < fast: # mandatory condition, but might be confusing
if slow < fast: # mandatory condition, but might be confusing
fast, slow = slow, fast
_length = max(tclength, fast, slow)
close = verify_series(close, _length)
offset = get_offset(offset)
# kwargs allows for three more series (ma1, ma2 and osc) which can be passed here
# ma1 and ma2 input negate internal ema calculations, osc substitutes both ma's.
if close is None: return
# kwargs allows for three more series (ma1, ma2 and osc) which can be passed
# here ma1 and ma2 input negate internal ema calculations, osc substitutes
# both ma's.
ma1 = kwargs.pop("ma1", False)
ma2 = kwargs.pop("ma2", False)
osc = kwargs.pop("osc", False)
# 3 different modes of calculation..
if isinstance(ma1, Series) and isinstance(ma2, Series) and not osc:
ma1 = verify_series(ma1)
ma2 = verify_series(ma2)
ma1 = verify_series(ma1, _length)
ma2 = verify_series(ma2, _length)
if ma1 is None or ma2 is None: return
# Calculate Result based on external feeded series
XMAC = ma1 - ma2
xmacd = ma1 - ma2
# invoke shared calculation
collect = schaff_tc(close, XMAC, tclen, factor)
pff, pf = schaff_tc(close, xmacd, tclength, factor)
elif isinstance(osc, Series):
osc = verify_series(osc)
# Calculate Result based on feeded oscillator (should be ranging around 0 x-axis)
XMAC = osc
osc = verify_series(osc, _length)
if osc is None: return
# Calculate Result based on feeded oscillator
# (should be ranging around 0 x-axis)
xmacd = osc
# invoke shared calculation
collect = schaff_tc(close, XMAC, tclen, factor)
pff, pf = schaff_tc(close, xmacd, tclength, factor)
else:
# Calculate Result .. (traditionel/full)
# MACD line
fastma = ema(close, length=fast)
slowma = ema(close, length=slow)
XMAC = fastma - slowma
xmacd = fastma - slowma
# invoke shared calculation
collect = schaff_tc(close, XMAC, tclen, factor)
pff, pf = schaff_tc(close, xmacd, tclength, factor)
# Resulting Series
stc = Series(collect[0], index=close.index)
macd = Series(XMAC, index=close.index)
stoch = Series(collect[1], index=close.index)
stc = Series(pff, index=close.index)
macd = Series(xmacd, index=close.index)
stoch = Series(pf, index=close.index)
# Offset
if offset != 0:
@@ -109,7 +77,7 @@ def stc(close, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwa
stoch.fillna(method=kwargs["fill_method"], inplace=True)
# Name and Categorize it
_props = f"_{tclen}_{fast}_{slow}_{factor}"
_props = f"_{tclength}_{fast}_{slow}_{factor}"
stc.name = f"STC{_props}"
macd.name = f"STCmacd{_props}"
stoch.name = f"STCstoch{_props}"
@@ -127,15 +95,17 @@ def stc(close, tclen=None, fast=None, slow=None, factor=None, offset=None, **kwa
stc.__doc__ = \
"""Schaff Trend Cycle (STC)
The Schaff Trend Cycle is an evolution of the popular MACD incorportating two cascaded
stochastic calculations with additional smoothing.
The STC returns also the beginning MACD result as well as the result after the first stochastic
including its smoothing. This implementation has been extended for Pandas TA to
also allow for separatly feeding any other two moving Averages (as ma1 and ma2) or to skip this
to feed an oscillator (osc), based on which the Schaff Trend Cycle should be calculated.
The Schaff Trend Cycle is an evolution of the popular MACD incorportating two
cascaded stochastic calculations with additional smoothing.
The STC returns also the beginning MACD result as well as the result after the
first stochastic including its smoothing. This implementation has been extended
for Pandas TA to also allow for separatly feeding any other two moving Averages
(as ma1 and ma2) or to skip this to feed an oscillator (osc), based on which the
Schaff Trend Cycle should be calculated.
Feed external moving averages:
Internally calculation..
Internally calculation..
stc = ta.stc(close=df["close"], tclen=stc_tclen, fast=ma1_interval, slow=ma2_interval, factor=stc_factor)
becomes..
extMa1 = df.ta.zlma(close=df["close"], length=ma1_interval, append=True)
@@ -146,14 +116,14 @@ The same goes for osc=, which allows the input of an externally calculated oscil
Sources:
Implemented by rengel8 based on work found here:
Implemented by rengel8 based on work found here:
https://www.prorealcode.com/prorealtime-indicators/schaff-trend-cycle2/
Calculation:
STCmacd = Moving Average Convergance/Divergance or Oscillator
Calculation:
STCmacd = Moving Average Convergance/Divergance or Oscillator
STCstoch = Intermediate Stochastic of MACD/Osc.
2nd Stochastic including filtering with results in the
STC = Schaff Trend Cycle
2nd Stochastic including filtering with results in the
STC = Schaff Trend Cycle
Args:
close (pd.Series): Series of 'close's, used for indexing Series, mandatory
@@ -173,3 +143,41 @@ Kwargs:
Returns:
pd.DataFrame: stc, macd, stoch
"""
def schaff_tc(close, xmacd, tclength, factor):
# ACTUAL Calculation part, which is shared between operation modes
# 1St : Stochastic of MACD
lowest_xmacd = xmacd.rolling(tclength).min() # min value in interval tclen
xmacd_range = non_zero_range(xmacd.rolling(tclength).max(), lowest_xmacd)
m = len(xmacd)
# %Fast K of MACD
stoch1, pf = list(xmacd), list(xmacd)
stoch1[0], pf[0] = 0, 0
for i in range(1, m):
if lowest_xmacd[i] > 0:
stoch1[i] = 100 * ((xmacd[i] - lowest_xmacd[i]) / xmacd_range[i])
else:
stoch1[i] = stoch1[i - 1]
# Smoothed Calculation for % Fast D of MACD
pf[i] = round(pf[i - 1] + (factor * (stoch1[i] - pf[i - 1])), 8)
pf = Series(pf, index=close.index)
# 2nd : Stochastic of smoothed Percent Fast D, 'PF', above
lowest_pf = pf.rolling(tclength).min()
pf_range = non_zero_range(pf.rolling(tclength).max(), lowest_pf)
# % of Fast K of PF
stoch2, pff = list(xmacd), list(xmacd)
stoch2[0], pff[0] = 0, 0
for i in range(1, m):
if pf_range[i] > 0:
stoch2[i] = 100 * ((pf[i] - lowest_pf[i]) / pf_range[i])
else:
stoch2[i] = stoch2[i - 1]
# Smoothed Calculation for % Fast D of PF
pff[i] = round(pff[i - 1] + (factor * (stoch2[i] - pff[i - 1])), 8)
return [pff, pf]
+27 -12
View File
@@ -32,7 +32,8 @@ def calmar_ratio(close: Series, method: str = "percent", years: int = 3) -> floa
Args:
close (pd.Series): Series of 'close's
method (str): Max DD calculation options: 'dollar', 'percent', 'log'. Default: 'dollar'
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)
@@ -56,7 +57,8 @@ def downside_deviation(returns: Series, benchmark_rate: float = 0.0, tf: str = "
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'
tf (str): Time Frame options: 'days', 'weeks', 'months', and 'years'.
Default: 'years'
>>> result = ta.downside_deviation(returns, benchmark_rate=0.0, tf="years")
"""
@@ -106,8 +108,10 @@ def max_drawdown(close: Series, method:str = None, all:bool = False) -> float:
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
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)
"""
@@ -136,8 +140,11 @@ def optimal_leverage(
Args:
close (pd.Series): Series of 'close's
benchmark_rate (float): Benchmark Rate to use. Default: 0.0
period (int, float): Period to use to calculate Mean Annual Return and Annual Standard Deviation. Default: None or the default sharpe_ratio.period()
log (bool): If True, calculates log_return. Otherwise it returns percent_return. Default: False
period (int, float): Period to use to calculate Mean Annual Return and
Annual Standard Deviation.
Default: None or the default sharpe_ratio.period()
log (bool): If True, calculates log_return. Otherwise it returns
percent_return. Default: False
>>> result = ta.optimal_leverage(close, benchmark_rate=0.0, log=False)
"""
@@ -181,9 +188,12 @@ def sharpe_ratio(close: Series, benchmark_rate: float = 0.0, log: bool = False,
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
log (bool): If True, calculates log_return. Otherwise it returns
percent_return. Default: False
use_cagr (bool): Use cagr - benchmark_rate instead. Default: False
period (int, float): Period to use to calculate Mean Annual Return and Annual Standard Deviation. Default: RATE["TRADING_DAYS_PER_YEAR"] (currently 252)
period (int, float): Period to use to calculate Mean Annual Return and
Annual Standard Deviation.
Default: RATE["TRADING_DAYS_PER_YEAR"] (currently 252)
>>> result = ta.sharpe_ratio(close, benchmark_rate=0.0, log=False)
"""
@@ -204,7 +214,8 @@ def sortino_ratio(close: Series, benchmark_rate: float = 0.0, log: bool = False)
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
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)
"""
@@ -221,9 +232,13 @@ def volatility(close: Series, tf: str = "years", returns: bool = False, log: boo
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
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)
"""
+1 -1
View File
@@ -18,7 +18,7 @@ setup(
"pandas_ta.volatility",
"pandas_ta.volume"
],
version=".".join(("0", "2", "68b")),
version=".".join(("0", "2", "69b")),
description=long_description,
long_description=long_description,
author="Kevin Johnson",
+5
View File
@@ -197,6 +197,11 @@ class TestMomentumExtension(TestCase):
["SQZ_ON", "SQZ_OFF", "SQZ_NO", "SQZhlr_20_2.0_20_1.5"]
)
def test_stc_ext(self):
self.data.ta.stc(append=True)
self.assertIsInstance(self.data, DataFrame)
self.assertEqual(list(self.data.columns[-3:]), ["STC_10_12_26_0.5", "STCmacd_10_12_26_0.5", "STCstoch_10_12_26_0.5"])
def test_stoch_ext(self):
self.data.ta.stoch(append=True)
self.assertIsInstance(self.data, DataFrame)
+5
View File
@@ -349,6 +349,11 @@ class TestMomentum(TestCase):
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "SQZhlr_20_2.0_20_1.5_LB")
def test_stc(self):
result = pandas_ta.stc(self.close)
self.assertIsInstance(result, DataFrame)
self.assertEqual(result.name, "STC_10_12_26_0.5")
# @skip
def test_stoch(self):
# TV Correlation